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
0a61262f
Commit
0a61262f
authored
Aug 15, 2017
by
Fatih Acet
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'repo-fixes' into 'master'
Many Repo Fixes See merge request !13432
parents
8615e785
2024198d
Changes
35
Show whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
516 additions
and
437 deletions
+516
-437
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+0
-1
app/assets/javascripts/project.js
app/assets/javascripts/project.js
+1
-1
app/assets/javascripts/repo/components/repo.vue
app/assets/javascripts/repo/components/repo.vue
+28
-24
app/assets/javascripts/repo/components/repo_commit_section.vue
...ssets/javascripts/repo/components/repo_commit_section.vue
+59
-27
app/assets/javascripts/repo/components/repo_edit_button.vue
app/assets/javascripts/repo/components/repo_edit_button.vue
+22
-13
app/assets/javascripts/repo/components/repo_editor.vue
app/assets/javascripts/repo/components/repo_editor.vue
+45
-40
app/assets/javascripts/repo/components/repo_file.vue
app/assets/javascripts/repo/components/repo_file.vue
+54
-13
app/assets/javascripts/repo/components/repo_file_buttons.vue
app/assets/javascripts/repo/components/repo_file_buttons.vue
+37
-10
app/assets/javascripts/repo/components/repo_file_options.vue
app/assets/javascripts/repo/components/repo_file_options.vue
+1
-1
app/assets/javascripts/repo/components/repo_loading_file.vue
app/assets/javascripts/repo/components/repo_loading_file.vue
+43
-18
app/assets/javascripts/repo/components/repo_prev_directory.vue
...ssets/javascripts/repo/components/repo_prev_directory.vue
+14
-2
app/assets/javascripts/repo/components/repo_sidebar.vue
app/assets/javascripts/repo/components/repo_sidebar.vue
+1
-1
app/assets/javascripts/repo/components/repo_tab.vue
app/assets/javascripts/repo/components/repo_tab.vue
+4
-4
app/assets/javascripts/repo/components/repo_tabs.vue
app/assets/javascripts/repo/components/repo_tabs.vue
+1
-2
app/assets/javascripts/repo/helpers/monaco_loader_helper.js
app/assets/javascripts/repo/helpers/monaco_loader_helper.js
+2
-1
app/assets/javascripts/repo/helpers/repo_helper.js
app/assets/javascripts/repo/helpers/repo_helper.js
+21
-50
app/assets/javascripts/repo/index.js
app/assets/javascripts/repo/index.js
+2
-0
app/assets/javascripts/repo/services/repo_service.js
app/assets/javascripts/repo/services/repo_service.js
+5
-11
app/assets/javascripts/repo/stores/repo_store.js
app/assets/javascripts/repo/stores/repo_store.js
+11
-37
app/assets/javascripts/vue_shared/components/popup_dialog.vue
...assets/javascripts/vue_shared/components/popup_dialog.vue
+0
-5
app/assets/stylesheets/framework/animations.scss
app/assets/stylesheets/framework/animations.scss
+78
-0
app/assets/stylesheets/framework/layout.scss
app/assets/stylesheets/framework/layout.scss
+0
-4
app/assets/stylesheets/pages/repo.scss
app/assets/stylesheets/pages/repo.scss
+12
-106
app/controllers/projects/blob_controller.rb
app/controllers/projects/blob_controller.rb
+5
-0
app/serializers/tree_root_entity.rb
app/serializers/tree_root_entity.rb
+14
-1
app/views/shared/_ref_switcher.html.haml
app/views/shared/_ref_switcher.html.haml
+1
-1
app/views/shared/_target_switcher.html.haml
app/views/shared/_target_switcher.html.haml
+1
-1
app/views/shared/repo/_repo.html.haml
app/views/shared/repo/_repo.html.haml
+7
-1
spec/javascripts/repo/components/repo_commit_section_spec.js
spec/javascripts/repo/components/repo_commit_section_spec.js
+30
-28
spec/javascripts/repo/components/repo_edit_button_spec.js
spec/javascripts/repo/components/repo_edit_button_spec.js
+7
-9
spec/javascripts/repo/components/repo_file_buttons_spec.js
spec/javascripts/repo/components/repo_file_buttons_spec.js
+5
-13
spec/javascripts/repo/components/repo_file_spec.js
spec/javascripts/repo/components/repo_file_spec.js
+3
-3
spec/javascripts/repo/components/repo_loading_file_spec.js
spec/javascripts/repo/components/repo_loading_file_spec.js
+1
-1
spec/javascripts/repo/components/repo_sidebar_spec.js
spec/javascripts/repo/components/repo_sidebar_spec.js
+1
-0
spec/javascripts/repo/components/repo_tabs_spec.js
spec/javascripts/repo/components/repo_tabs_spec.js
+0
-8
No files found.
app/assets/javascripts/api.js
View file @
0a61262f
...
...
@@ -97,7 +97,6 @@ const Api = {
},
commitMultiple
(
id
,
data
,
callback
)
{
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const
url
=
Api
.
buildUrl
(
Api
.
commitPath
)
.
replace
(
'
:id
'
,
id
);
return
$
.
ajax
({
...
...
app/assets/javascripts/project.js
View file @
0a61262f
...
...
@@ -126,7 +126,7 @@ import Cookies from 'js-cookie';
var
$form
=
$dropdown
.
closest
(
'
form
'
);
var
$visit
=
$dropdown
.
data
(
'
visit
'
);
var
shouldVisit
=
typeof
$visit
===
'
undefined
'
?
true
:
$visit
;
var
shouldVisit
=
$visit
?
true
:
$visit
;
var
action
=
$form
.
attr
(
'
action
'
);
var
divider
=
action
.
indexOf
(
'
?
'
)
===
-
1
?
'
?
'
:
'
&
'
;
if
(
shouldVisit
)
{
...
...
app/assets/javascripts/repo/components/repo.vue
View file @
0a61262f
...
...
@@ -14,13 +14,13 @@ export default {
data
:
()
=>
Store
,
mixins
:
[
RepoMixin
],
components
:
{
'
repo-sidebar
'
:
RepoSidebar
,
'
repo-tabs
'
:
RepoTabs
,
'
repo-file-buttons
'
:
RepoFileButtons
,
RepoSidebar
,
RepoTabs
,
RepoFileButtons
,
'
repo-editor
'
:
MonacoLoaderHelper
.
repoEditorLoader
,
'
repo-commit-section
'
:
RepoCommitSection
,
'
popup-dialog
'
:
PopupDialog
,
'
repo-preview
'
:
RepoPreview
,
RepoCommitSection
,
PopupDialog
,
RepoPreview
,
},
mounted
()
{
...
...
@@ -28,12 +28,12 @@ export default {
},
methods
:
{
dialogToggled
(
toggle
)
{
toggleDialogOpen
(
toggle
)
{
this
.
dialog
.
open
=
toggle
;
},
dialogSubmitted
(
status
)
{
this
.
dialog
.
open
=
false
;
this
.
toggleDialogOpen
(
false
)
;
this
.
dialog
.
status
=
status
;
},
...
...
@@ -43,21 +43,25 @@ export default {
</
script
>
<
template
>
<div
class=
"repository-view tree-content-holder"
>
<repo-sidebar/><div
class=
"panel-right"
:class=
"
{'edit-mode': editMode}">
<div
class=
"repository-view tree-content-holder"
>
<repo-sidebar/><div
v-if=
"isMini"
class=
"panel-right"
:class=
"
{'edit-mode': editMode}">
<repo-tabs/>
<component
:is=
"currentBlobView"
class=
"blob-viewer-container"
></component>
<component
:is=
"currentBlobView"
class=
"blob-viewer-container"
/>
<repo-file-buttons/>
</div>
<repo-commit-section/>
<popup-dialog
v-show=
"dialog.open"
:primary-button-label=
"__('Discard changes')"
:open=
"dialog.open"
kind=
"warning"
:title=
"__('Are you sure?')"
:body=
"__('Are you sure you want to discard your changes?')"
@
toggle=
"dialogToggled
"
@
toggle=
"toggleDialogOpen
"
@
submit=
"dialogSubmitted"
/>
</div>
</div>
</
template
>
app/assets/javascripts/repo/components/repo_commit_section.vue
View file @
0a61262f
...
...
@@ -2,7 +2,6 @@
/* global Flash */
import
Store
from
'
../stores/repo_store
'
;
import
RepoMixin
from
'
../mixins/repo_mixin
'
;
import
Helper
from
'
../helpers/repo_helper
'
;
import
Service
from
'
../services/repo_service
'
;
export
default
{
...
...
@@ -11,9 +10,12 @@ export default {
mixins
:
[
RepoMixin
],
computed
:
{
showCommitable
()
{
return
this
.
isCommitable
&&
this
.
changedFiles
.
length
;
},
branchPaths
()
{
const
branch
=
Helper
.
getBranch
();
return
this
.
changedFiles
.
map
(
f
=>
Helper
.
getFilePathFromFullPath
(
f
.
url
,
branch
));
return
this
.
changedFiles
.
map
(
f
=>
f
.
path
);
},
cantCommitYet
()
{
...
...
@@ -28,11 +30,10 @@ export default {
methods
:
{
makeCommit
()
{
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const
branch
=
Helper
.
getBranch
();
const
commitMessage
=
this
.
commitMessage
;
const
actions
=
this
.
changedFiles
.
map
(
f
=>
({
action
:
'
update
'
,
file_path
:
Helper
.
getFilePathFromFullPath
(
f
.
url
,
branch
)
,
file_path
:
f
.
path
,
content
:
f
.
newContent
,
}));
const
payload
=
{
...
...
@@ -47,49 +48,80 @@ export default {
resetCommitState
()
{
this
.
submitCommitsLoading
=
false
;
this
.
changedFiles
=
[];
this
.
openedFiles
=
[];
this
.
commitMessage
=
''
;
this
.
editMode
=
false
;
$
(
'
html, body
'
).
animate
({
scrollTop
:
0
},
'
fast
'
);
window
.
scrollTo
(
0
,
0
);
},
},
};
</
script
>
<
template
>
<div
id=
"commit-area"
v-if=
"isCommitable && changedFiles.length"
>
<form
class=
"form-horizontal"
>
<div
v-if=
"showCommitable"
id=
"commit-area"
>
<form
class=
"form-horizontal"
@
submit.prevent=
"makeCommit"
>
<fieldset>
<div
class=
"form-group"
>
<label
class=
"col-md-4 control-label staged-files"
>
Staged files (
{{
changedFiles
.
length
}}
)
</label>
<div
class=
"col-md-4"
>
<label
class=
"col-md-4 control-label staged-files"
>
Staged files (
{{
changedFiles
.
length
}}
)
</label>
<div
class=
"col-md-6"
>
<ul
class=
"list-unstyled changed-files"
>
<li
v-for=
"file in branchPaths"
:key=
"file.id"
>
<span
class=
"help-block"
>
{{
file
}}
</span>
<li
v-for=
"branchPath in branchPaths"
:key=
"branchPath"
>
<span
class=
"help-block"
>
{{
branchPath
}}
</span>
</li>
</ul>
</div>
</div>
<!-- Textarea
-->
<div
class=
"form-group"
>
<label
class=
"col-md-4 control-label"
for=
"commit-message"
>
Commit message
</label>
<div
class=
"col-md-4"
>
<textarea
class=
"form-control"
id=
"commit-message"
name=
"commit-message"
v-model=
"commitMessage"
></textarea>
<label
class=
"col-md-4 control-label"
for=
"commit-message"
>
Commit message
</label>
<div
class=
"col-md-6"
>
<textarea
id=
"commit-message"
class=
"form-control"
name=
"commit-message"
v-model=
"commitMessage"
>
</textarea>
</div>
</div>
<!-- Button Drop Down
-->
<div
class=
"form-group target-branch"
>
<label
class=
"col-md-4 control-label"
for=
"target-branch"
>
Target branch
</label>
<div
class=
"col-md-4"
>
<span
class=
"help-block"
>
{{
targetBranch
}}
</span>
<label
class=
"col-md-4 control-label"
for=
"target-branch"
>
Target branch
</label>
<div
class=
"col-md-6"
>
<span
class=
"help-block"
>
{{
targetBranch
}}
</span>
</div>
</div>
<div
class=
"col-md-offset-4 col-md-4"
>
<button
type=
"submit"
:disabled=
"cantCommitYet"
class=
"btn btn-success submit-commit"
@
click.prevent=
"makeCommit"
>
<i
class=
"fa fa-spinner fa-spin"
v-if=
"submitCommitsLoading"
></i>
<span
class=
"commit-summary"
>
Commit
{{
changedFiles
.
length
}}
{{
filePluralize
}}
</span>
<div
class=
"col-md-offset-4 col-md-6"
>
<button
ref=
"submitCommit"
type=
"submit"
:disabled=
"cantCommitYet"
class=
"btn btn-success"
>
<i
v-if=
"submitCommitsLoading"
class=
"fa fa-spinner fa-spin"
aria-hidden=
"true"
aria-label=
"loading"
>
</i>
<span
class=
"commit-summary"
>
Commit
{{
changedFiles
.
length
}}
{{
filePluralize
}}
</span>
</button>
</div>
</fieldset>
...
...
app/assets/javascripts/repo/components/repo_edit_button.vue
View file @
0a61262f
...
...
@@ -10,12 +10,15 @@ export default {
return
this
.
editMode
?
this
.
__
(
'
Cancel edit
'
)
:
this
.
__
(
'
Edit
'
);
},
buttonIcon
()
{
return
this
.
editMode
?
[]
:
[
'
fa
'
,
'
fa-pencil
'
];
showButton
()
{
return
this
.
isCommitable
&&
!
this
.
activeFile
.
render_error
&&
!
this
.
binary
&&
this
.
openedFiles
.
length
;
},
},
methods
:
{
editClicked
()
{
editC
ancelC
licked
()
{
if
(
this
.
changedFiles
.
length
)
{
this
.
dialog
.
open
=
true
;
return
;
...
...
@@ -24,15 +27,11 @@ export default {
Store
.
toggleBlobView
();
},
toggleProjectRefsForm
()
{
if
(
this
.
editMode
)
{
$
(
'
.project-refs-form
'
).
addClass
(
'
disabled-content
'
);
$
(
'
.project-refs-target-form
'
).
show
();
}
else
{
$
(
'
.project-refs-form
'
).
removeClass
(
'
disabled-content
'
);
$
(
'
.project-refs-target-form
'
).
hide
();
}
$
(
'
.project-refs-form
'
).
toggleClass
(
'
disabled
'
,
this
.
editMode
);
$
(
'
.js-tree-ref-target-holder
'
).
toggle
(
this
.
editMode
);
},
},
watch
:
{
editMode
()
{
this
.
toggleProjectRefsForm
();
...
...
@@ -42,8 +41,18 @@ export default {
</
script
>
<
template
>
<button
class=
"btn btn-default"
@
click.prevent=
"editClicked"
v-cloak
v-if=
"isCommitable && !activeFile.render_error"
:disabled=
"binary"
>
<i
:class=
"buttonIcon"
></i>
<span>
{{
buttonLabel
}}
</span>
<button
v-if=
"showButton"
class=
"btn btn-default"
type=
"button"
@
click.prevent=
"editCancelClicked"
>
<i
v-if=
"!editMode"
class=
"fa fa-pencil"
aria-hidden=
"true"
>
</i>
<span>
{{
buttonLabel
}}
</span>
</button>
</
template
>
app/assets/javascripts/repo/components/repo_editor.vue
View file @
0a61262f
...
...
@@ -8,68 +8,78 @@ const RepoEditor = {
data
:
()
=>
Store
,
destroyed
()
{
// this.monacoInstance.getModels().forEach((m) => {
// m.dispose();
// });
this
.
monacoInstance
.
destroy
();
if
(
Helper
.
monacoInstance
)
{
Helper
.
monacoInstance
.
destroy
();
}
},
mounted
()
{
Service
.
getRaw
(
this
.
activeFile
.
raw_path
)
.
then
((
rawResponse
)
=>
{
Store
.
blobRaw
=
rawResponse
.
data
;
Helper
.
findOpenedFileFromActive
()
.
plain
=
rawResponse
.
data
;
Store
.
activeFile
.
plain
=
rawResponse
.
data
;
const
monacoInstance
=
this
.
monaco
.
editor
.
create
(
this
.
$el
,
{
const
monacoInstance
=
Helper
.
monaco
.
editor
.
create
(
this
.
$el
,
{
model
:
null
,
readOnly
:
false
,
contextmenu
:
false
,
});
Store
.
monacoInstance
=
monacoInstance
;
Helper
.
monacoInstance
=
monacoInstance
;
this
.
addMonacoEvents
();
const
languages
=
this
.
monaco
.
languages
.
getLanguages
();
const
languageID
=
Helper
.
getLanguageIDForFile
(
this
.
activeFile
,
languages
);
const
newModel
=
this
.
monaco
.
editor
.
createModel
(
this
.
blobRaw
,
languageID
);
this
.
monacoInstance
.
setModel
(
newModel
);
}).
catch
(
Helper
.
loadingError
);
this
.
setupEditor
();
})
.
catch
(
Helper
.
loadingError
);
},
methods
:
{
setupEditor
()
{
this
.
showHide
();
Helper
.
setMonacoModelFromLanguage
();
},
showHide
()
{
if
(
!
this
.
openedFiles
.
length
||
(
this
.
binary
&&
!
this
.
activeFile
.
raw
))
{
this
.
$el
.
style
.
display
=
'
none
'
;
}
else
{
this
.
$el
.
style
.
display
=
'
inline-block
'
;
}
},
addMonacoEvents
()
{
this
.
monacoInstance
.
onMouseUp
(
this
.
onMonacoEditorMouseUp
);
this
.
monacoInstance
.
onKeyUp
(
this
.
onMonacoEditorKeysPressed
.
bind
(
this
));
Helper
.
monacoInstance
.
onMouseUp
(
this
.
onMonacoEditorMouseUp
);
Helper
.
monacoInstance
.
onKeyUp
(
this
.
onMonacoEditorKeysPressed
.
bind
(
this
));
},
onMonacoEditorKeysPressed
()
{
Store
.
setActiveFileContents
(
this
.
monacoInstance
.
getValue
());
Store
.
setActiveFileContents
(
Helper
.
monacoInstance
.
getValue
());
},
onMonacoEditorMouseUp
(
e
)
{
if
(
!
e
.
target
.
position
)
return
;
const
lineNumber
=
e
.
target
.
position
.
lineNumber
;
if
(
e
.
target
.
element
.
class
Name
===
'
line-numbers
'
)
{
if
(
e
.
target
.
element
.
class
List
.
contains
(
'
line-numbers
'
)
)
{
location
.
hash
=
`L
${
lineNumber
}
`
;
Store
.
activeLine
=
lineNumber
;
}
},
},
watch
:
{
activeLine
()
{
this
.
monacoInstance
.
setPosition
({
Helper
.
monacoInstance
.
setPosition
({
lineNumber
:
this
.
activeLine
,
column
:
1
,
});
}
},
},
watch
:
{
dialog
:
{
handler
(
obj
)
{
const
newObj
=
obj
;
if
(
newObj
.
status
)
{
newObj
.
status
=
false
;
this
.
openedFiles
.
map
((
file
)
=>
{
this
.
openedFiles
=
this
.
openedFiles
.
map
((
file
)
=>
{
const
f
=
file
;
if
(
f
.
active
)
{
this
.
blobRaw
=
f
.
plain
;
...
...
@@ -80,21 +90,16 @@ const RepoEditor = {
return
f
;
});
this
.
editMode
=
false
;
Store
.
toggleBlobView
();
}
},
deep
:
true
,
},
blobRaw
()
{
if
(
this
.
isTree
)
return
;
this
.
monacoInstance
.
setModel
(
null
);
const
languages
=
this
.
monaco
.
languages
.
getLanguages
();
const
languageID
=
Helper
.
getLanguageIDForFile
(
this
.
activeFile
,
languages
);
const
newModel
=
this
.
monaco
.
editor
.
createModel
(
this
.
blobRaw
,
languageID
);
this
.
monacoInstance
.
setModel
(
newModel
);
if
(
Helper
.
monacoInstance
&&
!
this
.
isTree
)
{
this
.
setupEditor
();
}
},
},
computed
:
{
...
...
app/assets/javascripts/repo/components/repo_file.vue
View file @
0a61262f
...
...
@@ -33,6 +33,26 @@ const RepoFile = {
canShowFile
()
{
return
!
this
.
loading
.
tree
||
this
.
hasFiles
;
},
fileIcon
()
{
const
classObj
=
{
'
fa-spinner fa-spin
'
:
this
.
file
.
loading
,
[
this
.
file
.
icon
]:
!
this
.
file
.
loading
,
};
return
classObj
;
},
fileIndentation
()
{
return
{
'
margin-left
'
:
`
${
this
.
file
.
level
*
10
}
px`
,
};
},
activeFileClass
()
{
return
{
active
:
this
.
activeFile
.
url
===
this
.
file
.
url
,
};
},
},
methods
:
{
...
...
@@ -46,21 +66,42 @@ export default RepoFile;
</
script
>
<
template
>
<tr
class=
"file"
v-if=
"canShowFile"
:class=
"
{'active': activeFile.url === file.url}">
<td
@
click.prevent=
"linkClicked(file)"
>
<i
class=
"fa file-icon"
v-if=
"!file.loading"
:class=
"file.icon"
:style=
"
{'margin-left': file.level * 10 + 'px'}">
</i>
<i
class=
"fa fa-spinner fa-spin"
v-if=
"file.loading"
:style=
"
{'margin-left': file.level * 10 + 'px'}">
</i>
<a
:href=
"file.url"
class=
"repo-file-name"
:title=
"file.url"
>
{{
file
.
name
}}
</a>
<tr
v-if=
"canShowFile"
class=
"file"
:class=
"activeFileClass"
@
click.prevent=
"linkClicked(file)"
>
<td>
<i
class=
"fa fa-fw file-icon"
:class=
"fileIcon"
:style=
"fileIndentation"
aria-label=
"file icon"
>
</i>
<a
:href=
"file.url"
class=
"repo-file-name"
:title=
"file.url"
>
{{
file
.
name
}}
</a>
</td>
<td
v-if=
"!isMini"
class=
"hidden-sm hidden-xs"
>
<template
v-if=
"!isMini"
>
<td
class=
"hidden-sm hidden-xs"
>
<div
class=
"commit-message"
>
<a
:href=
"file.lastCommitUrl"
>
{{
file
.
lastCommitMessage
}}
</a>
<a
@
click
.
stop
:href=
"file.lastCommitUrl"
>
{{
file
.
lastCommitMessage
}}
</a>
</div>
</td>
<td
v-if=
"!isMini"
class=
"hidden-xs"
>
<span
class=
"commit-update"
:title=
"tooltipTitle(file.lastCommitUpdate)"
>
{{
timeFormated
(
file
.
lastCommitUpdate
)
}}
</span>
<td
class=
"hidden-xs"
>
<span
class=
"commit-update"
:title=
"tooltipTitle(file.lastCommitUpdate)"
>
{{
timeFormated
(
file
.
lastCommitUpdate
)
}}
</span>
</td>
</
template
>
</tr>
</template>
app/assets/javascripts/repo/components/repo_file_buttons.vue
View file @
0a61262f
...
...
@@ -15,7 +15,7 @@ const RepoFileButtons = {
},
canPreview
()
{
return
Helper
.
is
KindaBinary
();
return
Helper
.
is
Renderable
();
},
},
...
...
@@ -28,15 +28,42 @@ export default RepoFileButtons;
</
script
>
<
template
>
<div
id=
"repo-file-buttons"
v-if=
"isMini"
>
<a
:href=
"activeFile.raw_path"
target=
"_blank"
class=
"btn btn-default raw"
rel=
"noopener noreferrer"
>
{{
rawDownloadButtonLabel
}}
</a>
<div
id=
"repo-file-buttons"
>
<a
:href=
"activeFile.raw_path"
target=
"_blank"
class=
"btn btn-default raw"
rel=
"noopener noreferrer"
>
{{
rawDownloadButtonLabel
}}
</a>
<div
class=
"btn-group"
role=
"group"
aria-label=
"File actions"
>
<a
:href=
"activeFile.blame_path"
class=
"btn btn-default blame"
>
Blame
</a>
<a
:href=
"activeFile.commits_path"
class=
"btn btn-default history"
>
History
</a>
<a
:href=
"activeFile.permalink"
class=
"btn btn-default permalink"
>
Permalink
</a>
<div
class=
"btn-group"
role=
"group"
aria-label=
"File actions"
>
<a
:href=
"activeFile.blame_path"
class=
"btn btn-default blame"
>
Blame
</a>
<a
:href=
"activeFile.commits_path"
class=
"btn btn-default history"
>
History
</a>
<a
:href=
"activeFile.permalink"
class=
"btn btn-default permalink"
>
Permalink
</a>
</div>
<a
href=
"#"
v-if=
"canPreview"
@
click.prevent=
"rawPreviewToggle"
class=
"btn btn-default preview"
>
{{
activeFileLabel
}}
</a>
</div>
<a
v-if=
"canPreview"
href=
"#"
@
click.prevent=
"rawPreviewToggle"
class=
"btn btn-default preview"
>
{{
activeFileLabel
}}
</a>
</div>
</
template
>
app/assets/javascripts/repo/components/repo_file_options.vue
View file @
0a61262f
...
...
@@ -17,7 +17,7 @@ export default RepoFileOptions;
</
script
>
<
template
>
<tr
v-if=
"isMini"
class=
"repo-file-options"
>
<tr
v-if=
"isMini"
class=
"repo-file-options"
>
<td>
<span
class=
"title"
>
{{
projectName
}}
</span>
</td>
...
...
app/assets/javascripts/repo/components/repo_loading_file.vue
View file @
0a61262f
...
...
@@ -18,9 +18,15 @@ const RepoLoadingFile = {
},
},
computed
:
{
showGhostLines
()
{
return
this
.
loading
.
tree
&&
!
this
.
hasFiles
;
},
},
methods
:
{
lineOfCode
(
n
)
{
return
`
line-of-cod
e-
${
n
}
`
;
return
`
skeleton-lin
e-
${
n
}
`
;
},
},
};
...
...
@@ -29,23 +35,42 @@ export default RepoLoadingFile;
</
script
>
<
template
>
<tr
v-if=
"loading.tree && !hasFiles"
class=
"loading-file"
>
<tr
v-if=
"showGhostLines"
class=
"loading-file"
>
<td>
<div
class=
"animation-container animation-container-small"
>
<div
v-for=
"n in 6"
:class=
"lineOfCode(n)"
:key=
"n"
></div>
<div
class=
"animation-container animation-container-small"
>
<div
v-for=
"n in 6"
:key=
"n"
:class=
"lineOfCode(n)"
>
</div>
</div>
</td>
<td
v-if=
"!isMini"
class=
"hidden-sm hidden-xs"
>
<td
v-if=
"!isMini"
class=
"hidden-sm hidden-xs"
>
<div
class=
"animation-container"
>
<div
v-for=
"n in 6"
:class=
"lineOfCode(n)"
:key=
"n"
></div>
<div
v-for=
"n in 6"
:key=
"n"
:class=
"lineOfCode(n)"
>
</div>
</div>
</td>
<td
v-if=
"!isMini"
class=
"hidden-xs"
>
<td
v-if=
"!isMini"
class=
"hidden-xs"
>
<div
class=
"animation-container animation-container-small"
>
<div
v-for=
"n in 6"
:class=
"lineOfCode(n)"
:key=
"n"
></div>
<div
v-for=
"n in 6"
:key=
"n"
:class=
"lineOfCode(n)"
>
</div>
</div>
</td>
</tr>
</tr>
</
template
>
app/assets/javascripts/repo/components/repo_prev_directory.vue
View file @
0a61262f
<
script
>
import
RepoMixin
from
'
../mixins/repo_mixin
'
;
const
RepoPreviousDirectory
=
{
props
:
{
prevUrl
:
{
...
...
@@ -7,6 +9,14 @@ const RepoPreviousDirectory = {
},
},
mixins
:
[
RepoMixin
],
computed
:
{
colSpanCondition
()
{
return
this
.
isMini
?
undefined
:
3
;
},
},
methods
:
{
linkClicked
(
file
)
{
this
.
$emit
(
'
linkclicked
'
,
file
);
...
...
@@ -19,8 +29,10 @@ export default RepoPreviousDirectory;
<
template
>
<tr
class=
"prev-directory"
>
<td
colspan=
"3"
>
<a
:href=
"prevUrl"
@
click.prevent=
"linkClicked(prevUrl)"
>
..
</a>
<td
:colspan=
"colSpanCondition"
@
click.prevent=
"linkClicked(prevUrl)"
>
<a
:href=
"prevUrl"
>
..
</a>
</td>
</tr>
</
template
>
app/assets/javascripts/repo/components/repo_sidebar.vue
View file @
0a61262f
...
...
@@ -35,7 +35,7 @@ export default {
fileClicked
(
clickedFile
)
{
let
file
=
clickedFile
;
if
(
file
.
loading
)
return
;
file
.
loading
=
true
;
if
(
file
.
type
===
'
tree
'
&&
file
.
opened
)
{
file
=
Store
.
removeChildFilesOfTree
(
file
);
...
...
app/assets/javascripts/repo/components/repo_tab.vue
View file @
0a61262f
...
...
@@ -18,8 +18,8 @@ const RepoTab = {
},
changedClass
()
{
const
tabChangedObj
=
{
'
fa-times
'
:
!
this
.
tab
.
changed
,
'
fa-circle
'
:
this
.
tab
.
changed
,
'
fa-times
close-icon
'
:
!
this
.
tab
.
changed
,
'
fa-circle
unsaved-icon
'
:
this
.
tab
.
changed
,
};
return
tabChangedObj
;
},
...
...
@@ -39,11 +39,11 @@ export default RepoTab;
</
script
>
<
template
>
<li>
<li
@
click=
"tabClicked(tab)"
>
<a
href=
"#0"
class=
"close"
@
click.prevent=
"closeTab(tab)"
@
click.
stop.
prevent=
"closeTab(tab)"
:aria-label=
"closeLabel"
>
<i
class=
"fa"
...
...
app/assets/javascripts/repo/components/repo_tabs.vue
View file @
0a61262f
...
...
@@ -23,8 +23,7 @@ export default RepoTabs;
</
script
>
<
template
>
<ul
id=
"tabs"
v-if=
"isMini"
>
<ul
id=
"tabs"
>
<repo-tab
v-for=
"tab in openedFiles"
:key=
"tab.id"
...
...
app/assets/javascripts/repo/helpers/monaco_loader_helper.js
View file @
0a61262f
/* global monaco */
import
RepoEditor
from
'
../components/repo_editor.vue
'
;
import
Store
from
'
../stores/repo_store
'
;
import
Helper
from
'
../helpers/repo_helper
'
;
import
monacoLoader
from
'
../monaco_loader
'
;
function
repoEditorLoader
()
{
Store
.
monacoLoading
=
true
;
return
new
Promise
((
resolve
,
reject
)
=>
{
monacoLoader
([
'
vs/editor/editor.main
'
],
()
=>
{
Store
.
monaco
=
monaco
;
Helper
.
monaco
=
monaco
;
Store
.
monacoLoading
=
false
;
resolve
(
RepoEditor
);
},
()
=>
{
...
...
app/assets/javascripts/repo/helpers/repo_helper.js
View file @
0a61262f
...
...
@@ -4,6 +4,8 @@ import Store from '../stores/repo_store';
import
'
../../flash
'
;
const
RepoHelper
=
{
monacoInstance
:
null
,
getDefaultActiveFile
()
{
return
{
active
:
true
,
...
...
@@ -37,10 +39,6 @@ const RepoHelper = {
return
fileName
.
split
(
'
.
'
).
pop
();
},
getBranch
()
{
return
$
(
'
button.dropdown-menu-toggle
'
).
attr
(
'
data-ref
'
);
},
getLanguageIDForFile
(
file
,
langs
)
{
const
ext
=
RepoHelper
.
getFileExtension
(
file
.
name
);
const
foundLang
=
RepoHelper
.
findLanguage
(
ext
,
langs
);
...
...
@@ -48,8 +46,12 @@ const RepoHelper = {
return
foundLang
?
foundLang
.
id
:
'
plaintext
'
;
},
getFilePathFromFullPath
(
fullPath
,
branch
)
{
return
fullPath
.
split
(
`
${
Store
.
projectUrl
}
/blob/
${
branch
}
`
)[
1
];
setMonacoModelFromLanguage
()
{
RepoHelper
.
monacoInstance
.
setModel
(
null
);
const
languages
=
RepoHelper
.
monaco
.
languages
.
getLanguages
();
const
languageID
=
RepoHelper
.
getLanguageIDForFile
(
Store
.
activeFile
,
languages
);
const
newModel
=
RepoHelper
.
monaco
.
editor
.
createModel
(
Store
.
blobRaw
,
languageID
);
RepoHelper
.
monacoInstance
.
setModel
(
newModel
);
},
findLanguage
(
ext
,
langs
)
{
...
...
@@ -66,7 +68,7 @@ const RepoHelper = {
return
file
;
},
is
KindaBinary
()
{
is
Renderable
()
{
const
okExts
=
[
'
md
'
,
'
svg
'
];
return
okExts
.
indexOf
(
Store
.
activeFile
.
extension
)
>
-
1
;
},
...
...
@@ -80,22 +82,8 @@ const RepoHelper = {
.
catch
(
RepoHelper
.
loadingError
);
},
toggleFakeTab
(
loading
,
file
)
{
if
(
loading
)
return
Store
.
addPlaceholderFile
();
return
Store
.
removeFromOpenedFiles
(
file
);
},
setLoading
(
loading
,
file
)
{
if
(
Service
.
url
.
indexOf
(
'
blob
'
)
>
-
1
)
{
Store
.
loading
.
blob
=
loading
;
return
RepoHelper
.
toggleFakeTab
(
loading
,
file
);
}
if
(
Service
.
url
.
indexOf
(
'
tree
'
)
>
-
1
)
Store
.
loading
.
tree
=
loading
;
return
undefined
;
},
// when you open a directory you need to put the directory files under
// the directory... This will merge the list of the current directory and the new list.
getNewMergedList
(
inDirectory
,
currentList
,
newList
)
{
const
newListSorted
=
newList
.
sort
(
this
.
compareFilesCaseInsensitive
);
if
(
!
inDirectory
)
return
newListSorted
;
...
...
@@ -104,6 +92,9 @@ const RepoHelper = {
return
RepoHelper
.
mergeNewListToOldList
(
newListSorted
,
currentList
,
inDirectory
,
indexOfFile
);
},
// within the get new merged list this does the merging of the current list of files
// and the new list of files. The files are never "in" another directory they just
// appear like they are because of the margin.
mergeNewListToOldList
(
newList
,
oldList
,
inDirectory
,
indexOfFile
)
{
newList
.
reverse
().
forEach
((
newFile
)
=>
{
const
fileIndex
=
indexOfFile
+
1
;
...
...
@@ -141,11 +132,9 @@ const RepoHelper = {
getContent
(
treeOrFile
)
{
let
file
=
treeOrFile
;
// const loadingData = RepoHelper.setLoading(true);
return
Service
.
getContent
()
.
then
((
response
)
=>
{
const
data
=
response
.
data
;
// RepoHelper.setLoading(false, loadingData);
Store
.
isTree
=
RepoHelper
.
isTree
(
data
);
if
(
!
Store
.
isTree
)
{
if
(
!
file
)
file
=
data
;
...
...
@@ -246,36 +235,18 @@ const RepoHelper = {
},
dataToListOfFiles
(
data
)
{
const
a
=
[];
// push in blobs
data
.
blobs
.
forEach
((
blob
)
=>
{
a
.
push
(
RepoHelper
.
serializeBlob
(
blob
));
});
data
.
trees
.
forEach
((
tree
)
=>
{
a
.
push
(
RepoHelper
.
serializeTree
(
tree
));
});
data
.
submodules
.
forEach
((
submodule
)
=>
{
a
.
push
(
RepoHelper
.
serializeSubmodule
(
submodule
));
});
return
a
;
const
{
blobs
,
trees
,
submodules
}
=
data
;
return
[
...
blobs
.
map
(
blob
=>
RepoHelper
.
serializeBlob
(
blob
)),
...
trees
.
map
(
tree
=>
RepoHelper
.
serializeTree
(
tree
)),
...
submodules
.
map
(
submodule
=>
RepoHelper
.
serializeSubmodule
(
submodule
)),
];
},
genKey
()
{
return
RepoHelper
.
Time
.
now
().
toFixed
(
3
);
},
getStateKey
()
{
return
RepoHelper
.
key
;
},
setStateKey
(
key
)
{
RepoHelper
.
key
=
key
;
},
updateHistoryEntry
(
url
,
title
)
{
const
history
=
window
.
history
;
...
...
@@ -293,7 +264,7 @@ const RepoHelper = {
},
loadingError
()
{
Flash
(
'
Unable to load th
e file
at this time.
'
);
Flash
(
'
Unable to load th
is content
at this time.
'
);
},
};
...
...
app/assets/javascripts/repo/index.js
View file @
0a61262f
...
...
@@ -33,6 +33,8 @@ function setInitialStore(data) {
Store
.
projectId
=
data
.
projectId
;
Store
.
projectName
=
data
.
projectName
;
Store
.
projectUrl
=
data
.
projectUrl
;
Store
.
canCommit
=
data
.
canCommit
;
Store
.
onTopOfBranch
=
data
.
onTopOfBranch
;
Store
.
currentBranch
=
$
(
'
button.dropdown-menu-toggle
'
).
attr
(
'
data-ref
'
);
Store
.
checkIsCommitable
();
}
...
...
app/assets/javascripts/repo/services/repo_service.js
View file @
0a61262f
...
...
@@ -13,16 +13,6 @@ const RepoService = {
},
richExtensionRegExp
:
/md/
,
checkCurrentBranchIsCommitable
()
{
const
url
=
Store
.
service
.
refsUrl
;
return
axios
.
get
(
url
,
{
params
:
{
ref
:
Store
.
currentBranch
,
search
:
Store
.
currentBranch
,
},
});
},
getRaw
(
url
)
{
return
axios
.
get
(
url
,
{
// Stop Axios from parsing a JSON file into a JS object
...
...
@@ -77,7 +67,11 @@ const RepoService = {
commitFiles
(
payload
,
cb
)
{
Api
.
commitMultiple
(
Store
.
projectId
,
payload
,
(
data
)
=>
{
if
(
data
.
short_id
&&
data
.
stats
)
{
Flash
(
`Your changes have been committed. Commit
${
data
.
short_id
}
with
${
data
.
stats
.
additions
}
additions,
${
data
.
stats
.
deletions
}
deletions.`
,
'
notice
'
);
}
else
{
Flash
(
data
.
message
);
}
cb
();
});
},
...
...
app/assets/javascripts/repo/stores/repo_store.js
View file @
0a61262f
...
...
@@ -5,8 +5,9 @@ import Service from '../services/repo_service';
const
RepoStore
=
{
monaco
:
{},
monacoLoading
:
false
,
monacoInstance
:
{},
service
:
''
,
canCommit
:
false
,
onTopOfBranch
:
false
,
editMode
:
false
,
isTree
:
false
,
isRoot
:
false
,
...
...
@@ -52,14 +53,7 @@ const RepoStore = {
// mutations
checkIsCommitable
()
{
RepoStore
.
service
.
checkCurrentBranchIsCommitable
()
.
then
((
data
)
=>
{
// you shouldn't be able to make commits on commits or tags.
const
{
Branches
,
Commits
,
Tags
}
=
data
.
data
;
if
(
Branches
&&
Branches
.
length
)
RepoStore
.
isCommitable
=
true
;
if
(
Commits
&&
Commits
.
length
)
RepoStore
.
isCommitable
=
false
;
if
(
Tags
&&
Tags
.
length
)
RepoStore
.
isCommitable
=
false
;
}).
catch
(()
=>
Flash
(
'
Failed to check if branch can be committed to.
'
));
RepoStore
.
isCommitable
=
RepoStore
.
onTopOfBranch
&&
RepoStore
.
canCommit
;
},
addFilesToDirectory
(
inDirectory
,
currentList
,
newList
)
{
...
...
@@ -117,15 +111,15 @@ const RepoStore = {
removeChildFilesOfTree
(
tree
)
{
let
foundTree
=
false
;
const
treeToClose
=
tree
;
let
wereDone
=
false
;
let
canStopSearching
=
false
;
RepoStore
.
files
=
RepoStore
.
files
.
filter
((
file
)
=>
{
const
isItTheTreeWeWant
=
file
.
url
===
treeToClose
.
url
;
// if it's the next tree
if
(
foundTree
&&
file
.
type
===
'
tree
'
&&
!
isItTheTreeWeWant
&&
file
.
level
===
treeToClose
.
level
)
{
wereDone
=
true
;
canStopSearching
=
true
;
return
true
;
}
if
(
wereDone
)
return
true
;
if
(
canStopSearching
)
return
true
;
if
(
isItTheTreeWeWant
)
foundTree
=
true
;
...
...
@@ -142,8 +136,8 @@ const RepoStore = {
if
(
file
.
type
===
'
tree
'
)
return
;
let
foundIndex
;
RepoStore
.
openedFiles
=
RepoStore
.
openedFiles
.
filter
((
openedFile
,
i
)
=>
{
if
(
openedFile
.
url
===
file
.
url
)
foundIndex
=
i
;
return
openedFile
.
url
!==
file
.
url
;
if
(
openedFile
.
path
===
file
.
path
)
foundIndex
=
i
;
return
openedFile
.
path
!==
file
.
path
;
});
// now activate the right tab based on what you closed.
...
...
@@ -157,36 +151,16 @@ const RepoStore = {
return
;
}
if
(
foundIndex
)
{
if
(
foundIndex
>
0
)
{
if
(
foundIndex
&&
foundIndex
>
0
)
{
RepoStore
.
setActiveFiles
(
RepoStore
.
openedFiles
[
foundIndex
-
1
]);
}
}
},
addPlaceholderFile
()
{
const
randomURL
=
Helper
.
Time
.
now
();
const
newFakeFile
=
{
active
:
false
,
binary
:
true
,
type
:
'
blob
'
,
loading
:
true
,
mime_type
:
'
loading
'
,
name
:
'
loading
'
,
url
:
randomURL
,
fake
:
true
,
};
RepoStore
.
openedFiles
.
push
(
newFakeFile
);
return
newFakeFile
;
},
addToOpenedFiles
(
file
)
{
const
openFile
=
file
;
const
openedFilesAlreadyExists
=
RepoStore
.
openedFiles
.
some
(
openedFile
=>
openedFile
.
url
===
openFile
.
url
);
.
some
(
openedFile
=>
openedFile
.
path
===
openFile
.
path
);
if
(
openedFilesAlreadyExists
)
return
;
...
...
app/assets/javascripts/vue_shared/components/popup_dialog.vue
View file @
0a61262f
...
...
@@ -3,10 +3,6 @@ export default {
name
:
'
popup-dialog
'
,
props
:
{
open
:
{
type
:
Boolean
,
required
:
true
,
},
title
:
{
type
:
String
,
required
:
true
,
...
...
@@ -53,7 +49,6 @@ export default {
<
template
>
<div
class=
"modal popup-dialog"
v-if=
"open"
role=
"dialog"
tabindex=
"-1"
>
<div
class=
"modal-dialog"
role=
"document"
>
...
...
app/assets/stylesheets/framework/animations.scss
View file @
0a61262f
...
...
@@ -187,3 +187,81 @@ a {
.fade-in-full
{
animation
:
fadeInFull
$fade-in-duration
1
;
}
.animation-container
{
background
:
$repo-editor-grey
;
height
:
40px
;
overflow
:
hidden
;
position
:
relative
;
&
.animation-container-small
{
height
:
12px
;
}
&
:
:
before
{
animation-duration
:
1s
;
animation-fill-mode
:
forwards
;
animation-iteration-count
:
infinite
;
animation-name
:
blockTextShine
;
animation-timing-function
:
linear
;
background-image
:
$repo-editor-linear-gradient
;
background-repeat
:
no-repeat
;
background-size
:
800px
45px
;
content
:
' '
;
display
:
block
;
height
:
100%
;
position
:
relative
;
}
div
{
background
:
$white-light
;
height
:
6px
;
left
:
0
;
position
:
absolute
;
right
:
0
;
}
.skeleton-line-1
{
left
:
0
;
top
:
8px
;
}
.skeleton-line-2
{
left
:
150px
;
top
:
0
;
height
:
10px
;
}
.skeleton-line-3
{
left
:
0
;
top
:
23px
;
}
.skeleton-line-4
{
left
:
0
;
top
:
38px
;
}
.skeleton-line-5
{
left
:
200px
;
top
:
28px
;
height
:
10px
;
}
.skeleton-line-6
{
top
:
14px
;
left
:
230px
;
height
:
10px
;
}
}
@keyframes
blockTextShine
{
0
%
{
transform
:
translateX
(
-468px
);
}
100
%
{
transform
:
translateX
(
468px
);
}
}
app/assets/stylesheets/framework/layout.scss
View file @
0a61262f
...
...
@@ -117,10 +117,6 @@ body {
margin-top
:
$header-height
+
$performance-bar-height
;
}
[
v-cloak
]
{
display
:
none
;
}
.vertical-center
{
min-height
:
100vh
;
display
:
flex
;
...
...
app/assets/stylesheets/pages/repo.scss
View file @
0a61262f
.fade-enter-active
,
.fade-leave-active
{
transition
:
opacity
.5s
;
transition
:
opacity
$sidebar-transition-duration
;
}
.monaco-loader
{
...
...
@@ -85,7 +85,7 @@
}
.blob-viewer-container
{
height
:
calc
(
100vh
-
6
3
px
);
height
:
calc
(
100vh
-
6
2
px
);
overflow
:
auto
;
}
...
...
@@ -109,6 +109,7 @@
border-right
:
1px
solid
$white-dark
;
border-bottom
:
1px
solid
$white-dark
;
white-space
:
nowrap
;
cursor
:
pointer
;
&
.remove
{
animation
:
swipeRightDissapear
ease-in
0
.1s
;
...
...
@@ -131,6 +132,7 @@
width
:
100px
;
text-align
:
center
;
vertical-align
:
middle
;
text-decoration
:
none
;
&
.close
{
width
:
auto
;
...
...
@@ -140,15 +142,15 @@
}
}
i
.fa.fa-times
,
i
.fa.fa-circle
{
.close-icon
,
.unsaved-icon
{
float
:
right
;
margin-top
:
3px
;
margin-left
:
15px
;
color
:
$gray-darkest
;
}
i
.fa.fa-circle
{
.unsaved-icon
{
color
:
$brand-success
;
}
...
...
@@ -198,7 +200,7 @@
background
:
$gray-light
;
padding
:
20px
;
span
.help-block
{
.help-block
{
padding-top
:
7px
;
margin-top
:
0
;
}
...
...
@@ -226,6 +228,7 @@
vertical-align
:
top
;
width
:
20%
;
border-right
:
1px
solid
$white-normal
;
min-height
:
475px
;
height
:
calc
(
100vh
+
20px
);
overflow
:
auto
;
}
...
...
@@ -255,7 +258,6 @@
text-transform
:
uppercase
;
font-weight
:
bold
;
color
:
$gray-darkest
;
width
:
185px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
...
...
@@ -264,7 +266,7 @@
}
}
.f
a
{
.f
ile-icon
{
margin-right
:
5px
;
}
...
...
@@ -274,118 +276,22 @@
}
a
{
@include
str-truncated
(
250px
);
color
:
$almost-black
;
display
:
inline-block
;
vertical-align
:
middle
;
}
ul
{
list-style-type
:
none
;
padding
:
0
;
li
{
border-bottom
:
1px
solid
$border-gray-normal
;
padding
:
10px
20px
;
a
{
color
:
$almost-black
;
}
.fa
{
font-size
:
12px
;
margin-right
:
5px
;
}
}
}
}
}
.animation-container
{
background
:
$repo-editor-grey
;
height
:
40px
;
overflow
:
hidden
;
position
:
relative
;
&
.animation-container-small
{
height
:
12px
;
}
&
:
:
before
{
animation-duration
:
1s
;
animation-fill-mode
:
forwards
;
animation-iteration-count
:
infinite
;
animation-name
:
blockTextShine
;
animation-timing-function
:
linear
;
background-image
:
$repo-editor-linear-gradient
;
background-repeat
:
no-repeat
;
background-size
:
800px
45px
;
content
:
' '
;
display
:
block
;
height
:
100%
;
position
:
relative
;
}
div
{
background
:
$white-light
;
height
:
6px
;
left
:
0
;
position
:
absolute
;
right
:
0
;
}
.line-of-code-1
{
left
:
0
;
top
:
8px
;
}
.line-of-code-2
{
left
:
150px
;
top
:
0
;
height
:
10px
;
}
.line-of-code-3
{
left
:
0
;
top
:
23px
;
}
.line-of-code-4
{
left
:
0
;
top
:
38px
;
}
.line-of-code-5
{
left
:
200px
;
top
:
28px
;
height
:
10px
;
}
.line-of-code-6
{
top
:
14px
;
left
:
230px
;
height
:
10px
;
}
}
.render-error
{
min-height
:
calc
(
100vh
-
6
3
px
);
min-height
:
calc
(
100vh
-
6
2
px
);
p
{
width
:
100%
;
}
}
@keyframes
blockTextShine
{
0
%
{
transform
:
translateX
(
-468px
);
}
100
%
{
transform
:
translateX
(
468px
);
}
}
@keyframes
swipeRightAppear
{
0
%
{
transform
:
scaleX
(
0
.00
);
...
...
app/controllers/projects/blob_controller.rb
View file @
0a61262f
...
...
@@ -198,6 +198,10 @@ class Projects::BlobController < Projects::ApplicationController
json
=
blob_json
(
@blob
)
return
render_404
unless
json
path_segments
=
@path
.
split
(
'/'
)
path_segments
.
pop
tree_path
=
path_segments
.
join
(
'/'
)
render
json:
json
.
merge
(
path:
blob
.
path
,
name:
blob
.
name
,
...
...
@@ -212,6 +216,7 @@ class Projects::BlobController < Projects::ApplicationController
raw_path:
project_raw_path
(
project
,
@id
),
blame_path:
project_blame_path
(
project
,
@id
),
commits_path:
project_commits_path
(
project
,
@id
),
tree_path:
project_tree_path
(
project
,
File
.
join
(
@ref
,
tree_path
)),
permalink:
project_blob_path
(
project
,
File
.
join
(
@commit
.
id
,
@path
))
)
end
...
...
app/serializers/tree_root_entity.rb
View file @
0a61262f
# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`.
class
TreeRootEntity
<
Grape
::
Entity
include
RequestAwareEntity
expose
:path
expose
:trees
,
using:
TreeEntity
expose
:blobs
,
using:
BlobEntity
expose
:submodules
,
using:
SubmoduleEntity
expose
:parent_tree_url
do
|
tree
|
path
=
tree
.
path
.
sub
(
%r{
\A
/}
,
''
)
next
unless
path
.
present?
path_segments
=
path
.
split
(
'/'
)
path_segments
.
pop
parent_tree_path
=
path_segments
.
join
(
'/'
)
project_tree_path
(
request
.
project
,
File
.
join
(
request
.
ref
,
parent_tree_path
))
end
end
app/views/shared/_ref_switcher.html.haml
View file @
0a61262f
...
...
@@ -6,7 +6,7 @@
-
@options
&&
@options
.
each
do
|
key
,
value
|
=
hidden_field_tag
key
,
value
,
id:
nil
.dropdown
=
dropdown_toggle
dropdown_toggle_text
,
{
toggle:
"dropdown"
,
selected:
dropdown_toggle_text
,
ref:
@ref
,
refs_url:
refs_project_path
(
@project
),
field_name:
'ref'
,
submit_form_on_click:
true
},
{
toggle_class:
"js-project-refs-dropdown"
}
=
dropdown_toggle
dropdown_toggle_text
,
{
toggle:
"dropdown"
,
selected:
dropdown_toggle_text
,
ref:
@ref
,
refs_url:
refs_project_path
(
@project
),
field_name:
'ref'
,
submit_form_on_click:
true
,
visit:
true
},
{
toggle_class:
"js-project-refs-dropdown"
}
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown
{
class:
(
"dropdown-menu-align-right"
if
local_assigns
[
:align_right
])
}
=
dropdown_title
_
(
"Switch branch/tag"
)
=
dropdown_filter
_
(
"Search branches and tags"
)
...
...
app/views/shared/_target_switcher.html.haml
View file @
0a61262f
-
dropdown_toggle_text
=
@ref
||
@project
.
default_branch
=
form_tag
nil
,
method: :get
,
class:
"project-refs-target-form"
do
=
form_tag
nil
,
method: :get
,
style:
{
display:
'none'
},
class:
"project-refs-target-form"
do
=
hidden_field_tag
:destination
,
destination
-
if
defined?
(
path
)
=
hidden_field_tag
:path
,
path
...
...
app/views/shared/repo/_repo.html.haml
View file @
0a61262f
#repo
{
data:
{
url:
content_url
,
project_name:
project
.
name
,
refs_url:
refs_project_path
(
project
,
format: :json
),
project_url:
project_path
(
project
),
project_id:
project
.
id
,
can_commit:
(
!!
can_push_branch?
(
project
,
@ref
)).
to_s
}
}
#repo
{
data:
{
url:
content_url
,
project_name:
project
.
name
,
refs_url:
refs_project_path
(
project
,
format: :json
),
project_url:
project_path
(
project
),
project_id:
project
.
id
,
can_commit:
(
!!
can_push_branch?
(
project
,
@ref
)).
to_s
,
on_top_of_branch:
(
!!
on_top_of_branch?
(
project
,
@ref
)).
to_s
}
}
spec/javascripts/repo/components/repo_commit_section_spec.js
View file @
0a61262f
import
Vue
from
'
vue
'
;
import
repoCommitSection
from
'
~/repo/components/repo_commit_section.vue
'
;
import
RepoStore
from
'
~/repo/stores/repo_store
'
;
import
RepoHelper
from
'
~/repo/helpers/repo_helper
'
;
import
Api
from
'
~/api
'
;
describe
(
'
RepoCommitSection
'
,
()
=>
{
const
branch
=
'
master
'
;
const
projectUrl
=
'
projectUrl
'
;
const
open
edFiles
=
[{
const
chang
edFiles
=
[{
id
:
0
,
changed
:
true
,
url
:
`/namespace/
${
projectUrl
}
/blob/
${
branch
}
/dir/file0.ext`
,
path
:
'
dir/file0.ext
'
,
newContent
:
'
a
'
,
},
{
id
:
1
,
changed
:
true
,
url
:
`/namespace/
${
projectUrl
}
/blob/
${
branch
}
/dir/file1.ext`
,
path
:
'
dir/file1.ext
'
,
newContent
:
'
b
'
,
},
{
}];
const
openedFiles
=
changedFiles
.
concat
([{
id
:
2
,
url
:
`/namespace/
${
projectUrl
}
/blob/
${
branch
}
/dir/file2.ext`
,
path
:
'
dir/file2.ext
'
,
changed
:
false
,
}];
}]
)
;
RepoStore
.
projectUrl
=
projectUrl
;
function
createComponent
()
{
function
createComponent
(
el
)
{
const
RepoCommitSection
=
Vue
.
extend
(
repoCommitSection
);
return
new
RepoCommitSection
().
$mount
();
return
new
RepoCommitSection
().
$mount
(
el
);
}
it
(
'
renders a commit section
'
,
()
=>
{
RepoStore
.
isCommitable
=
true
;
RepoStore
.
currentBranch
=
branch
;
RepoStore
.
targetBranch
=
branch
;
RepoStore
.
openedFiles
=
openedFiles
;
spyOn
(
RepoHelper
,
'
getBranch
'
).
and
.
returnValue
(
branch
);
const
vm
=
createComponent
();
const
changedFiles
=
[...
vm
.
$el
.
querySelectorAll
(
'
.changed-files > li
'
)];
const
changedFile
Element
s
=
[...
vm
.
$el
.
querySelectorAll
(
'
.changed-files > li
'
)];
const
commitMessage
=
vm
.
$el
.
querySelector
(
'
#commit-message
'
);
const
submitCommit
=
vm
.
$
el
.
querySelector
(
'
.submit-commit
'
)
;
const
submitCommit
=
vm
.
$
refs
.
submitCommit
;
const
targetBranch
=
vm
.
$el
.
querySelector
(
'
.target-branch
'
);
expect
(
vm
.
$el
.
querySelector
(
'
:scope > form
'
)).
toBeTruthy
();
expect
(
vm
.
$el
.
querySelector
(
'
.staged-files
'
).
textContent
).
toEqual
(
'
Staged files (2)
'
);
expect
(
changedFiles
.
length
).
toEqual
(
2
);
expect
(
vm
.
$el
.
querySelector
(
'
.staged-files
'
).
textContent
.
trim
()
).
toEqual
(
'
Staged files (2)
'
);
expect
(
changedFile
Element
s
.
length
).
toEqual
(
2
);
changedFiles
.
forEach
((
changedFile
,
i
)
=>
{
const
filePath
=
RepoHelper
.
getFilePathFromFullPath
(
openedFiles
[
i
].
url
,
branch
);
expect
(
changedFile
.
textContent
).
toEqual
(
filePath
);
changedFileElements
.
forEach
((
changedFile
,
i
)
=>
{
expect
(
changedFile
.
textContent
.
trim
()).
toEqual
(
changedFiles
[
i
].
path
);
});
expect
(
commitMessage
.
tagName
).
toEqual
(
'
TEXTAREA
'
);
...
...
@@ -59,9 +59,9 @@ describe('RepoCommitSection', () => {
expect
(
submitCommit
.
type
).
toEqual
(
'
submit
'
);
expect
(
submitCommit
.
disabled
).
toBeTruthy
();
expect
(
submitCommit
.
querySelector
(
'
.fa-spinner.fa-spin
'
)).
toBeFalsy
();
expect
(
vm
.
$el
.
querySelector
(
'
.commit-summary
'
).
textContent
).
toEqual
(
'
Commit 2 files
'
);
expect
(
targetBranch
.
querySelector
(
'
:scope > label
'
).
textContent
).
toEqual
(
'
Target branch
'
);
expect
(
targetBranch
.
querySelector
(
'
.help-block
'
).
textContent
).
toEqual
(
branch
);
expect
(
vm
.
$el
.
querySelector
(
'
.commit-summary
'
).
textContent
.
trim
()
).
toEqual
(
'
Commit 2 files
'
);
expect
(
targetBranch
.
querySelector
(
'
:scope > label
'
).
textContent
.
trim
()
).
toEqual
(
'
Target branch
'
);
expect
(
targetBranch
.
querySelector
(
'
.help-block
'
).
textContent
.
trim
()
).
toEqual
(
branch
);
});
it
(
'
does not render if not isCommitable
'
,
()
=>
{
...
...
@@ -89,14 +89,20 @@ describe('RepoCommitSection', () => {
const
projectId
=
'
projectId
'
;
const
commitMessage
=
'
commitMessage
'
;
RepoStore
.
isCommitable
=
true
;
RepoStore
.
currentBranch
=
branch
;
RepoStore
.
targetBranch
=
branch
;
RepoStore
.
openedFiles
=
openedFiles
;
RepoStore
.
projectId
=
projectId
;
spyOn
(
RepoHelper
,
'
getBranch
'
).
and
.
returnValue
(
branch
);
// We need to append to body to get form `submit` events working
// Otherwise we run into, "Form submission canceled because the form is not connected"
// See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
const
el
=
document
.
createElement
(
'
div
'
);
document
.
body
.
appendChild
(
el
);
const
vm
=
createComponent
();
const
vm
=
createComponent
(
el
);
const
commitMessageEl
=
vm
.
$el
.
querySelector
(
'
#commit-message
'
);
const
submitCommit
=
vm
.
$
el
.
querySelector
(
'
.submit-commit
'
)
;
const
submitCommit
=
vm
.
$
refs
.
submitCommit
;
vm
.
commitMessage
=
commitMessage
;
...
...
@@ -124,10 +130,8 @@ describe('RepoCommitSection', () => {
expect
(
actions
[
1
].
action
).
toEqual
(
'
update
'
);
expect
(
actions
[
0
].
content
).
toEqual
(
openedFiles
[
0
].
newContent
);
expect
(
actions
[
1
].
content
).
toEqual
(
openedFiles
[
1
].
newContent
);
expect
(
actions
[
0
].
file_path
)
.
toEqual
(
RepoHelper
.
getFilePathFromFullPath
(
openedFiles
[
0
].
url
,
branch
));
expect
(
actions
[
1
].
file_path
)
.
toEqual
(
RepoHelper
.
getFilePathFromFullPath
(
openedFiles
[
1
].
url
,
branch
));
expect
(
actions
[
0
].
file_path
).
toEqual
(
openedFiles
[
0
].
path
);
expect
(
actions
[
1
].
file_path
).
toEqual
(
openedFiles
[
1
].
path
);
done
();
});
...
...
@@ -140,7 +144,6 @@ describe('RepoCommitSection', () => {
const
vm
=
{
submitCommitsLoading
:
true
,
changedFiles
:
new
Array
(
10
),
openedFiles
:
new
Array
(
10
),
commitMessage
:
'
commitMessage
'
,
editMode
:
true
,
};
...
...
@@ -149,7 +152,6 @@ describe('RepoCommitSection', () => {
expect
(
vm
.
submitCommitsLoading
).
toEqual
(
false
);
expect
(
vm
.
changedFiles
).
toEqual
([]);
expect
(
vm
.
openedFiles
).
toEqual
([]);
expect
(
vm
.
commitMessage
).
toEqual
(
''
);
expect
(
vm
.
editMode
).
toEqual
(
false
);
});
...
...
spec/javascripts/repo/components/repo_edit_button_spec.js
View file @
0a61262f
...
...
@@ -12,19 +12,21 @@ describe('RepoEditButton', () => {
it
(
'
renders an edit button that toggles the view state
'
,
(
done
)
=>
{
RepoStore
.
isCommitable
=
true
;
RepoStore
.
changedFiles
=
[];
RepoStore
.
binary
=
false
;
RepoStore
.
openedFiles
=
[{},
{}];
const
vm
=
createComponent
();
expect
(
vm
.
$el
.
tagName
).
toEqual
(
'
BUTTON
'
);
expect
(
vm
.
$el
.
textContent
).
toMatch
(
'
Edit
'
);
spyOn
(
vm
,
'
editClicked
'
).
and
.
callThrough
();
spyOn
(
vm
,
'
editC
ancelC
licked
'
).
and
.
callThrough
();
spyOn
(
vm
,
'
toggleProjectRefsForm
'
);
vm
.
$el
.
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
editClicked
).
toHaveBeenCalled
();
expect
(
vm
.
editC
ancelC
licked
).
toHaveBeenCalled
();
expect
(
vm
.
toggleProjectRefsForm
).
toHaveBeenCalled
();
expect
(
vm
.
$el
.
textContent
).
toMatch
(
'
Cancel edit
'
);
done
();
...
...
@@ -40,14 +42,10 @@ describe('RepoEditButton', () => {
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
editClicked
'
,
()
=>
{
it
(
'
sets dialog to open when there are changedFiles
'
,
()
=>
{
describe
(
'
editC
ancelC
licked
'
,
()
=>
{
it
(
'
sets dialog to open when there are changedFiles
'
);
});
it
(
'
toggles editMode and calls toggleBlobView
'
,
()
=>
{
});
it
(
'
toggles editMode and calls toggleBlobView
'
);
});
});
});
spec/javascripts/repo/components/repo_file_buttons_spec.js
View file @
0a61262f
...
...
@@ -32,13 +32,13 @@ describe('RepoFileButtons', () => {
expect
(
vm
.
$el
.
id
).
toEqual
(
'
repo-file-buttons
'
);
expect
(
raw
.
href
).
toMatch
(
`/
${
activeFile
.
raw_path
}
`
);
expect
(
raw
.
textContent
).
toEqual
(
'
Raw
'
);
expect
(
raw
.
textContent
.
trim
()
).
toEqual
(
'
Raw
'
);
expect
(
blame
.
href
).
toMatch
(
`/
${
activeFile
.
blame_path
}
`
);
expect
(
blame
.
textContent
).
toEqual
(
'
Blame
'
);
expect
(
blame
.
textContent
.
trim
()
).
toEqual
(
'
Blame
'
);
expect
(
history
.
href
).
toMatch
(
`/
${
activeFile
.
commits_path
}
`
);
expect
(
history
.
textContent
).
toEqual
(
'
History
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.permalink
'
).
textContent
).
toEqual
(
'
Permalink
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.preview
'
).
textContent
).
toEqual
(
activeFileLabel
);
expect
(
history
.
textContent
.
trim
()
).
toEqual
(
'
History
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.permalink
'
).
textContent
.
trim
()
).
toEqual
(
'
Permalink
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.preview
'
).
textContent
.
trim
()
).
toEqual
(
activeFileLabel
);
});
it
(
'
triggers rawPreviewToggle on preview click
'
,
()
=>
{
...
...
@@ -72,12 +72,4 @@ describe('RepoFileButtons', () => {
expect
(
vm
.
$el
.
querySelector
(
'
.preview
'
)).
toBeFalsy
();
});
it
(
'
does not render if not isMini
'
,
()
=>
{
RepoStore
.
openedFiles
=
[];
const
vm
=
createComponent
();
expect
(
vm
.
$el
.
innerHTML
).
toBeFalsy
();
});
});
spec/javascripts/repo/components/repo_file_spec.js
View file @
0a61262f
...
...
@@ -39,9 +39,9 @@ describe('RepoFile', () => {
expect
(
vm
.
$el
.
querySelector
(
`.
${
file
.
icon
}
`
).
style
.
marginLeft
).
toEqual
(
'
100px
'
);
expect
(
name
.
title
).
toEqual
(
file
.
url
);
expect
(
name
.
href
).
toMatch
(
`/
${
file
.
url
}
`
);
expect
(
name
.
textContent
).
toEqual
(
file
.
name
);
expect
(
vm
.
$el
.
querySelector
(
'
.commit-message
'
).
textContent
).
toBe
(
file
.
lastCommitMessage
);
expect
(
vm
.
$el
.
querySelector
(
'
.commit-update
'
).
textContent
).
toBe
(
updated
);
expect
(
name
.
textContent
.
trim
()
).
toEqual
(
file
.
name
);
expect
(
vm
.
$el
.
querySelector
(
'
.commit-message
'
).
textContent
.
trim
()
).
toBe
(
file
.
lastCommitMessage
);
expect
(
vm
.
$el
.
querySelector
(
'
.commit-update
'
).
textContent
.
trim
()
).
toBe
(
updated
);
expect
(
fileIcon
.
classList
.
contains
(
file
.
icon
)).
toBeTruthy
();
expect
(
fileIcon
.
style
.
marginLeft
).
toEqual
(
`
${
file
.
level
*
10
}
px`
);
});
...
...
spec/javascripts/repo/components/repo_loading_file_spec.js
View file @
0a61262f
...
...
@@ -13,7 +13,7 @@ describe('RepoLoadingFile', () => {
function
assertLines
(
lines
)
{
lines
.
forEach
((
line
,
n
)
=>
{
const
index
=
n
+
1
;
expect
(
line
.
classList
.
contains
(
`
line-of-cod
e-
${
index
}
`
)).
toBeTruthy
();
expect
(
line
.
classList
.
contains
(
`
skeleton-lin
e-
${
index
}
`
)).
toBeTruthy
();
});
}
...
...
spec/javascripts/repo/components/repo_sidebar_spec.js
View file @
0a61262f
...
...
@@ -15,6 +15,7 @@ describe('RepoSidebar', () => {
RepoStore
.
files
=
[{
id
:
0
,
}];
RepoStore
.
openedFiles
=
[];
const
vm
=
createComponent
();
const
thead
=
vm
.
$el
.
querySelector
(
'
thead
'
);
const
tbody
=
vm
.
$el
.
querySelector
(
'
tbody
'
);
...
...
spec/javascripts/repo/components/repo_tabs_spec.js
View file @
0a61262f
...
...
@@ -29,14 +29,6 @@ describe('RepoTabs', () => {
expect
(
tabs
[
2
].
classList
.
contains
(
'
tabs-divider
'
)).
toBeTruthy
();
});
it
(
'
does not render a tabs list if not isMini
'
,
()
=>
{
RepoStore
.
openedFiles
=
[];
const
vm
=
createComponent
();
expect
(
vm
.
$el
.
innerHTML
).
toBeFalsy
();
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
tabClosed
'
,
()
=>
{
it
(
'
calls removeFromOpenedFiles with file obj
'
,
()
=>
{
...
...
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