Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
c90b520d
Commit
c90b520d
authored
Nov 17, 2017
by
Phil Hughes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
created editor library to manage all things editor
[ci skip]
parent
37864fb0
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
344 additions
and
77 deletions
+344
-77
app/assets/javascripts/repo/components/repo_editor.vue
app/assets/javascripts/repo/components/repo_editor.vue
+10
-73
app/assets/javascripts/repo/lib/common/model.js
app/assets/javascripts/repo/lib/common/model.js
+46
-0
app/assets/javascripts/repo/lib/decorations/controller.js
app/assets/javascripts/repo/lib/decorations/controller.js
+46
-0
app/assets/javascripts/repo/lib/diff/controller.js
app/assets/javascripts/repo/lib/diff/controller.js
+73
-0
app/assets/javascripts/repo/lib/diff/worker.js
app/assets/javascripts/repo/lib/diff/worker.js
+78
-0
app/assets/javascripts/repo/lib/editor.js
app/assets/javascripts/repo/lib/editor.js
+69
-0
app/assets/javascripts/repo/services/index.js
app/assets/javascripts/repo/services/index.js
+4
-0
app/assets/stylesheets/pages/repo.scss
app/assets/stylesheets/pages/repo.scss
+18
-4
No files found.
app/assets/javascripts/repo/components/repo_editor.vue
View file @
c90b520d
...
@@ -3,21 +3,17 @@
...
@@ -3,21 +3,17 @@
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
flash
from
'
../../flash
'
;
import
flash
from
'
../../flash
'
;
import
monacoLoader
from
'
../monaco_loader
'
;
import
monacoLoader
from
'
../monaco_loader
'
;
import
editor
from
'
../lib/editor
'
;
export
default
{
export
default
{
destroyed
()
{
destroyed
()
{
if
(
this
.
monacoInstance
)
{
editor
.
dispose
();
this
.
monacoInstance
.
destroy
();
}
},
},
mounted
()
{
mounted
()
{
if
(
this
.
monaco
)
{
if
(
this
.
monaco
)
{
this
.
initMonaco
();
this
.
initMonaco
();
}
else
{
}
else
{
monacoLoader
([
'
vs/editor/editor.main
'
,
'
vs/editor/common/diff/diffComputer
'
],
(
_
,
{
DiffComputer
})
=>
{
monacoLoader
([
'
vs/editor/editor.main
'
],
()
=>
{
this
.
monaco
=
monaco
;
this
.
DiffComputer
=
DiffComputer
;
this
.
initMonaco
();
this
.
initMonaco
();
});
});
}
}
...
@@ -30,84 +26,25 @@ export default {
...
@@ -30,84 +26,25 @@ export default {
initMonaco
()
{
initMonaco
()
{
if
(
this
.
shouldHideEditor
)
return
;
if
(
this
.
shouldHideEditor
)
return
;
if
(
this
.
monacoInstance
)
{
editor
.
clearEditor
();
this
.
monacoInstance
.
setModel
(
null
);
}
this
.
getRawFileData
(
this
.
activeFile
)
this
.
getRawFileData
(
this
.
activeFile
)
.
then
(()
=>
{
.
then
(()
=>
{
if
(
!
this
.
monacoInstance
)
{
editor
.
createInstance
(
this
.
$el
);
this
.
monacoInstance
=
this
.
monaco
.
editor
.
create
(
this
.
$el
,
{
model
:
null
,
readOnly
:
false
,
contextmenu
:
true
,
scrollBeyondLastLine
:
false
,
});
this
.
languages
=
this
.
monaco
.
languages
.
getLanguages
();
}
this
.
setupEditor
();
})
})
.
then
(()
=>
this
.
setupEditor
())
.
catch
(()
=>
flash
(
'
Error setting up monaco. Please try again.
'
));
.
catch
(()
=>
flash
(
'
Error setting up monaco. Please try again.
'
));
},
},
setupEditor
()
{
setupEditor
()
{
if
(
!
this
.
activeFile
)
return
;
if
(
!
this
.
activeFile
)
return
;
const
content
=
this
.
activeFile
.
content
!==
''
?
this
.
activeFile
.
content
:
this
.
activeFile
.
raw
;
const
foundLang
=
this
.
languages
.
find
(
lang
=>
lang
.
extensions
&&
lang
.
extensions
.
indexOf
(
this
.
activeFileExtension
)
===
0
,
);
const
newModel
=
this
.
monaco
.
editor
.
createModel
(
content
,
foundLang
?
foundLang
.
id
:
'
plaintext
'
,
);
const
originalLines
=
this
.
monaco
.
editor
.
createModel
(
this
.
activeFile
.
raw
,
foundLang
?
foundLang
.
id
:
'
plaintext
'
,
).
getLinesContent
();
this
.
monacoInstance
.
setModel
(
newModel
);
this
.
decorations
=
[];
const
modifiedType
=
(
change
)
=>
{
if
(
change
.
originalEndLineNumber
===
0
)
{
return
'
added
'
;
}
else
if
(
change
.
modifiedEndLineNumber
===
0
)
{
return
'
removed
'
;
}
return
'
modified
'
;
};
this
.
monacoModelChangeContents
=
newModel
.
onDidChangeContent
(()
=>
{
const
diffComputer
=
new
this
.
DiffComputer
(
originalLines
,
newModel
.
getLinesContent
(),
{
shouldPostProcessCharChanges
:
true
,
shouldIgnoreTrimWhitespace
:
true
,
shouldMakePrettyDiff
:
true
,
},
);
this
.
decorations
=
this
.
monacoInstance
.
deltaDecorations
(
this
.
decorations
,
const
model
=
editor
.
createModel
(
this
.
activeFile
);
diffComputer
.
computeDiff
().
map
(
change
=>
({
range
:
new
monaco
.
Range
(
change
.
modifiedStartLineNumber
,
1
,
!
change
.
modifiedEndLineNumber
?
change
.
modifiedStartLineNumber
:
change
.
modifiedEndLineNumber
,
1
,
),
options
:
{
isWholeLine
:
true
,
linesDecorationsClassName
:
`dirty-diff dirty-diff-
${
modifiedType
(
change
)}
`
,
},
})),
);
editor
.
attachModel
(
model
);
model
.
onChange
((
m
)
=>
{
this
.
changeFileContent
({
this
.
changeFileContent
({
file
:
this
.
activeFile
,
file
:
this
.
activeFile
,
content
:
this
.
monacoInstance
.
getValue
(),
content
:
m
.
getValue
(),
});
});
});
});
},
},
...
...
app/assets/javascripts/repo/lib/common/model.js
0 → 100644
View file @
c90b520d
/* global monaco */
export
default
class
Model
{
constructor
(
file
)
{
this
.
file
=
file
;
this
.
content
=
file
.
content
!==
''
?
file
.
content
:
file
.
raw
;
this
.
originalModel
=
monaco
.
editor
.
createModel
(
this
.
content
,
undefined
,
new
monaco
.
Uri
(
null
,
null
,
`original/
${
this
.
file
.
path
}
`
),
);
this
.
model
=
monaco
.
editor
.
createModel
(
this
.
content
,
undefined
,
new
monaco
.
Uri
(
null
,
null
,
this
.
file
.
path
),
);
this
.
disposers
=
new
Map
();
}
get
url
()
{
return
this
.
model
.
uri
.
toString
();
}
getModel
()
{
return
this
.
model
;
}
getOriginalModel
()
{
return
this
.
originalModel
;
}
onChange
(
cb
)
{
this
.
disposers
.
set
(
this
.
file
.
path
,
this
.
model
.
onDidChangeContent
(
e
=>
cb
(
this
.
model
,
e
)),
);
}
dispose
()
{
this
.
model
.
dispose
();
this
.
originalModel
.
dispose
();
this
.
disposers
.
forEach
(
disposer
=>
disposer
.
dispose
());
this
.
disposers
.
clear
();
}
}
app/assets/javascripts/repo/lib/decorations/controller.js
0 → 100644
View file @
c90b520d
import
editor
from
'
../editor
'
;
class
DecorationsController
{
constructor
()
{
this
.
decorations
=
new
Map
();
this
.
editorDecorations
=
new
Map
();
}
getAllDecorationsForModel
(
model
)
{
if
(
!
this
.
decorations
.
has
(
model
.
url
))
return
[];
const
modelDecorations
=
this
.
decorations
.
get
(
model
.
url
);
const
decorations
=
[];
modelDecorations
.
forEach
(
val
=>
decorations
.
push
(...
val
));
return
decorations
;
}
addDecorations
(
model
,
decorationsKey
,
decorations
)
{
const
decorationMap
=
this
.
decorations
.
get
(
model
.
url
)
||
new
Map
();
decorationMap
.
set
(
decorationsKey
,
decorations
);
this
.
decorations
.
set
(
model
.
url
,
decorationMap
);
this
.
decorate
(
model
);
}
decorate
(
model
)
{
const
decorations
=
this
.
getAllDecorationsForModel
(
model
);
const
oldDecorations
=
this
.
editorDecorations
.
get
(
model
.
url
)
||
[];
this
.
editorDecorations
.
set
(
model
.
url
,
editor
.
instance
.
deltaDecorations
(
oldDecorations
,
decorations
),
);
}
dispose
()
{
this
.
decorations
.
clear
();
this
.
editorDecorations
.
clear
();
}
}
export
default
new
DecorationsController
();
app/assets/javascripts/repo/lib/diff/controller.js
0 → 100644
View file @
c90b520d
/* global monaco */
import
DirtyDiffWorker
from
'
./worker
'
;
import
decorationsController
from
'
../decorations/controller
'
;
export
const
getDiffChangeType
=
(
change
)
=>
{
if
(
change
.
originalEndLineNumber
===
0
)
{
return
'
added
'
;
}
else
if
(
change
.
modifiedEndLineNumber
===
0
)
{
return
'
removed
'
;
}
return
'
modified
'
;
};
export
const
getDecorator
=
change
=>
({
range
:
new
monaco
.
Range
(
change
.
modifiedStartLineNumber
,
1
,
!
change
.
modifiedEndLineNumber
?
change
.
modifiedStartLineNumber
:
change
.
modifiedEndLineNumber
,
1
,
),
options
:
{
isWholeLine
:
true
,
linesDecorationsClassName
:
`dirty-diff dirty-diff-
${
getDiffChangeType
(
change
)}
`
,
},
});
export
const
decorate
=
(
model
,
changes
)
=>
{
const
decorations
=
changes
.
map
(
change
=>
getDecorator
(
change
));
decorationsController
.
addDecorations
(
model
,
'
dirtyDiff
'
,
decorations
);
};
export
default
class
DirtyDiffController
{
constructor
()
{
this
.
editorSimpleWorker
=
null
;
this
.
models
=
new
Map
();
this
.
worker
=
new
DirtyDiffWorker
();
}
attachModel
(
model
)
{
if
(
this
.
models
.
has
(
model
.
getModel
().
uri
.
toString
()))
return
;
[
model
.
getModel
(),
model
.
getOriginalModel
()].
forEach
((
iModel
)
=>
{
this
.
worker
.
attachModel
({
url
:
iModel
.
uri
.
toString
(),
versionId
:
iModel
.
getVersionId
(),
lines
:
iModel
.
getLinesContent
(),
EOL
:
'
\n
'
,
});
});
model
.
onChange
((
_
,
e
)
=>
this
.
computeDiff
(
model
,
e
));
this
.
models
.
set
(
model
.
getModel
().
uri
.
toString
(),
model
);
}
computeDiff
(
model
,
e
)
{
this
.
worker
.
modelChanged
(
model
,
e
);
this
.
worker
.
compute
(
model
,
changes
=>
decorate
(
model
,
changes
));
}
// eslint-disable-next-line class-methods-use-this
reDecorate
(
model
)
{
decorationsController
.
decorate
(
model
);
}
dispose
()
{
this
.
models
.
clear
();
this
.
worker
.
dispose
();
decorationsController
.
dispose
();
}
}
app/assets/javascripts/repo/lib/diff/worker.js
0 → 100644
View file @
c90b520d
/* global monaco */
export
default
class
DirtyDiffWorker
{
constructor
()
{
this
.
editorSimpleWorker
=
null
;
this
.
models
=
new
Map
();
this
.
actions
=
new
Set
();
// eslint-disable-next-line promise/catch-or-return
monaco
.
editor
.
createWebWorker
({
moduleId
:
'
vs/editor/common/services/editorSimpleWorker
'
,
}).
getProxy
().
then
((
editorSimpleWorker
)
=>
{
this
.
editorSimpleWorker
=
editorSimpleWorker
;
this
.
ready
();
});
}
// loop through all the previous cached actions
// this way we don't block the user from editing the file
ready
()
{
this
.
actions
.
forEach
((
action
)
=>
{
const
methodName
=
Object
.
keys
(
action
)[
0
];
this
[
methodName
](...
action
[
methodName
]);
});
this
.
actions
.
clear
();
}
attachModel
(
model
)
{
if
(
this
.
editorSimpleWorker
&&
!
this
.
models
.
has
(
model
.
url
))
{
this
.
editorSimpleWorker
.
acceptNewModel
(
model
);
this
.
models
.
set
(
model
.
url
,
model
);
}
else
if
(
!
this
.
editorSimpleWorker
)
{
this
.
actions
.
add
({
attachModel
:
[
model
],
});
}
}
modelChanged
(
model
,
e
)
{
if
(
this
.
editorSimpleWorker
)
{
this
.
editorSimpleWorker
.
acceptModelChanged
(
model
.
getModel
().
uri
.
toString
(),
e
,
);
}
else
{
this
.
actions
.
add
({
modelChanged
:
[
model
,
e
],
});
}
}
compute
(
model
,
cb
)
{
if
(
this
.
editorSimpleWorker
)
{
// eslint-disable-next-line promise/catch-or-return
this
.
editorSimpleWorker
.
computeDiff
(
model
.
getOriginalModel
().
uri
.
toString
(),
model
.
getModel
().
uri
.
toString
(),
).
then
(
cb
);
}
else
{
this
.
actions
.
add
({
compute
:
[
model
,
cb
],
});
}
}
dispose
()
{
this
.
models
.
forEach
(
model
=>
this
.
editorSimpleWorker
.
acceptRemovedModel
(
model
.
url
),
);
this
.
models
.
clear
();
this
.
actions
.
clear
();
this
.
editorSimpleWorker
.
dispose
();
this
.
editorSimpleWorker
=
null
;
}
}
app/assets/javascripts/repo/lib/editor.js
0 → 100644
View file @
c90b520d
/* global monaco */
import
DirtyDiffController
from
'
./diff/controller
'
;
import
Model
from
'
./common/model
'
;
class
Editor
{
constructor
()
{
this
.
models
=
new
Map
();
this
.
diffComputers
=
new
Map
();
this
.
currentModel
=
null
;
this
.
instance
=
null
;
this
.
dirtyDiffController
=
null
;
}
createInstance
(
domElement
)
{
if
(
!
this
.
instance
)
{
this
.
instance
=
monaco
.
editor
.
create
(
domElement
,
{
model
:
null
,
readOnly
:
false
,
contextmenu
:
true
,
scrollBeyondLastLine
:
false
,
});
this
.
dirtyDiffController
=
new
DirtyDiffController
();
}
}
createModel
(
file
)
{
if
(
this
.
models
.
has
(
file
.
path
))
{
return
this
.
models
.
get
(
file
.
path
);
}
const
model
=
new
Model
(
file
);
this
.
models
.
set
(
file
.
path
,
model
);
return
model
;
}
attachModel
(
model
)
{
this
.
instance
.
setModel
(
model
.
getModel
());
this
.
dirtyDiffController
.
attachModel
(
model
);
this
.
currentModel
=
model
;
this
.
dirtyDiffController
.
reDecorate
(
model
);
}
clearEditor
()
{
if
(
this
.
instance
)
{
this
.
instance
.
setModel
(
null
);
}
}
dispose
()
{
// dispose main monaco instance
if
(
this
.
instance
)
{
this
.
instance
.
dispose
();
this
.
instance
=
null
;
}
// dispose of all the models
this
.
models
.
forEach
(
model
=>
model
.
dispose
());
this
.
models
.
clear
();
this
.
dirtyDiffController
.
dispose
();
this
.
dirtyDiffController
=
null
;
}
}
export
default
new
Editor
();
app/assets/javascripts/repo/services/index.js
View file @
c90b520d
...
@@ -16,6 +16,10 @@ export default {
...
@@ -16,6 +16,10 @@ export default {
return
Promise
.
resolve
(
file
.
content
);
return
Promise
.
resolve
(
file
.
content
);
}
}
if
(
file
.
raw
)
{
return
Promise
.
resolve
(
file
.
raw
);
}
return
Vue
.
http
.
get
(
file
.
rawPath
,
{
params
:
{
format
:
'
json
'
}
})
return
Vue
.
http
.
get
(
file
.
rawPath
,
{
params
:
{
format
:
'
json
'
}
})
.
then
(
res
=>
res
.
text
());
.
then
(
res
=>
res
.
text
());
},
},
...
...
app/assets/stylesheets/pages/repo.scss
View file @
c90b520d
...
@@ -70,6 +70,7 @@
...
@@ -70,6 +70,7 @@
.line-numbers
{
.line-numbers
{
cursor
:
pointer
;
cursor
:
pointer
;
min-width
:
initial
;
&
:hover
{
&
:hover
{
text-decoration
:
underline
;
text-decoration
:
underline
;
...
@@ -309,16 +310,29 @@
...
@@ -309,16 +310,29 @@
left
:
0
!
important
;
left
:
0
!
important
;
&
-modified
{
&
-modified
{
background-color
:
rgb
(
19
,
117
,
150
)
;
background-color
:
$blue-500
;
}
}
&
-added
{
&
-added
{
background-color
:
rgb
(
89
,
119
,
11
)
;
background-color
:
$green-600
;
}
}
&
-removed
{
&
-removed
{
height
:
4px
!
important
;
height
:
0
!
important
;
width
:
0
!
important
;
bottom
:
-2px
;
bottom
:
-2px
;
background-color
:
red
;
border-style
:
solid
;
border-width
:
5px
;
border-color
:
transparent
transparent
transparent
$red-500
;
&
:
:
before
{
content
:
''
;
position
:
absolute
;
left
:
0
;
top
:
0
;
width
:
100px
;
height
:
1px
;
background-color
:
rgba
(
$red-500
,
.5
);
}
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment