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
0
Merge Requests
0
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
Jérome Perrin
gitlab-ce
Commits
2024198d
Commit
2024198d
authored
Aug 15, 2017
by
Jacob Schatz
Committed by
Fatih Acet
Aug 15, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Many Repo Fixes
parent
8615e785
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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
<
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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
/* 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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
.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 @
2024198d
...
...
@@ -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 @
2024198d
# 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 @
2024198d
...
...
@@ -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 @
2024198d
-
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 @
2024198d
#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 @
2024198d
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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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 @
2024198d
...
...
@@ -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