Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
6d14d1a942da
Commit
26ff973b
authored
Dec 13, 2018
by
Oswaldo Ferreira
Committed by
Phil Hughes
Dec 13, 2018
Browse files
Allow suggesting single line changes in diffs
parent
70702ee74d1a
Changes
86
Hide whitespace changes
Inline
Side-by-side
app/assets/javascripts/api.js
View file @
6d14d1a9
...
...
@@ -25,6 +25,7 @@ const Api = {
userStatusPath
:
'
/api/:version/users/:id/status
'
,
userPostStatusPath
:
'
/api/:version/user/status
'
,
commitPath
:
'
/api/:version/projects/:id/repository/commits
'
,
applySuggestionPath
:
'
/api/:version/suggestions/:id/apply
'
,
commitPipelinesPath
:
'
/:project_id/commit/:sha/pipelines
'
,
branchSinglePath
:
'
/api/:version/projects/:id/repository/branches/:branch
'
,
createBranchPath
:
'
/api/:version/projects/:id/repository/branches
'
,
...
...
@@ -185,6 +186,12 @@ const Api = {
});
},
applySuggestion
(
id
)
{
const
url
=
Api
.
buildUrl
(
Api
.
applySuggestionPath
).
replace
(
'
:id
'
,
encodeURIComponent
(
id
));
return
axios
.
put
(
url
);
},
commitPipelines
(
projectId
,
sha
)
{
const
encodedProjectId
=
projectId
.
split
(
'
/
'
)
...
...
app/assets/javascripts/diffs/components/app.vue
View file @
6d14d1a9
...
...
@@ -42,6 +42,11 @@ export default {
type
:
Object
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
changesEmptyStateIllustration
:
{
type
:
String
,
required
:
false
,
...
...
@@ -208,6 +213,7 @@ export default {
v-for=
"file in diffFiles"
:key=
"file.newPath"
:file=
"file"
:help-page-path=
"helpPagePath"
:can-current-user-fork=
"canCurrentUserFork"
/>
</
template
>
...
...
app/assets/javascripts/diffs/components/diff_content.vue
View file @
6d14d1a9
...
...
@@ -23,6 +23,11 @@ export default {
type
:
Object
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
mapState
({
...
...
@@ -74,11 +79,13 @@ export default {
v-if=
"isInlineView"
:diff-file=
"diffFile"
:diff-lines=
"diffFile.highlighted_diff_lines || []"
:help-page-path=
"helpPagePath"
/>
<parallel-diff-view
v-if=
"isParallelView"
:diff-file=
"diffFile"
:diff-lines=
"diffFile.parallel_diff_lines || []"
:help-page-path=
"helpPagePath"
/>
</
template
>
<diff-viewer
...
...
app/assets/javascripts/diffs/components/diff_discussions.vue
View file @
6d14d1a9
...
...
@@ -13,6 +13,11 @@ export default {
type
:
Array
,
required
:
true
,
},
line
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
shouldCollapseDiscussions
:
{
type
:
Boolean
,
required
:
false
,
...
...
@@ -23,6 +28,11 @@ export default {
required
:
false
,
default
:
false
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
methods
:
{
...
mapActions
([
'
toggleDiscussion
'
]),
...
...
@@ -72,6 +82,8 @@ export default {
:render-diff-file=
"false"
:always-expanded=
"true"
:discussions-by-diff-order=
"true"
:line=
"line"
:help-page-path=
"helpPagePath"
@
noteDeleted=
"deleteNoteHandler"
>
<span
v-if=
"renderAvatarBadge"
slot=
"avatar-badge"
class=
"badge badge-pill"
>
...
...
app/assets/javascripts/diffs/components/diff_file.vue
View file @
6d14d1a9
...
...
@@ -23,6 +23,11 @@ export default {
type
:
Boolean
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
...
...
@@ -164,6 +169,7 @@ export default {
v-if=
"!isCollapsed && file.renderIt"
:class=
"
{ hidden: isCollapsed || file.too_large }"
:diff-file="file"
:help-page-path="helpPagePath"
/>
<gl-loading-icon
v-if=
"showLoadingIcon"
class=
"diff-content loading"
/>
<div
v-else-if=
"showExpandMessage"
class=
"nothing-here-block diff-collapsed"
>
...
...
app/assets/javascripts/diffs/components/diff_line_note_form.vue
View file @
6d14d1a9
...
...
@@ -94,6 +94,7 @@ export default {
ref=
"noteForm"
:is-editing=
"true"
:line-code=
"line.line_code"
:line=
"line"
save-button-title=
"Comment"
class=
"diff-comment-form"
@
cancelForm=
"handleCancelCommentForm"
...
...
app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
View file @
6d14d1a9
...
...
@@ -16,6 +16,11 @@ export default {
type
:
String
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
className
()
{
...
...
@@ -38,7 +43,12 @@ export default {
<tr
v-if=
"shouldRender"
:class=
"className"
class=
"notes_holder"
>
<td
class=
"notes_content"
colspan=
"3"
>
<div
class=
"content"
>
<diff-discussions
v-if=
"line.discussions.length"
:discussions=
"line.discussions"
/>
<diff-discussions
v-if=
"line.discussions.length"
:line=
"line"
:discussions=
"line.discussions"
:help-page-path=
"helpPagePath"
/>
<diff-line-note-form
v-if=
"line.hasForm"
:diff-file-hash=
"diffFileHash"
...
...
app/assets/javascripts/diffs/components/inline_diff_view.vue
View file @
6d14d1a9
...
...
@@ -17,6 +17,11 @@ export default {
type
:
Array
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
mapGetters
(
'
diffs
'
,
[
'
commitId
'
]),
...
...
@@ -47,6 +52,7 @@ export default {
:key=
"`icr-$
{index}`"
:diff-file-hash="diffFile.file_hash"
:line="line"
:help-page-path="helpPagePath"
/>
</
template
>
</tbody>
...
...
app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
View file @
6d14d1a9
...
...
@@ -20,6 +20,11 @@ export default {
type
:
Number
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
hasExpandedDiscussionOnLeft
()
{
...
...
@@ -87,6 +92,8 @@ export default {
<diff-discussions
v-if=
"line.left.discussions.length"
:discussions=
"line.left.discussions"
:line=
"line.left"
:help-page-path=
"helpPagePath"
/>
</div>
<diff-line-note-form
...
...
@@ -102,6 +109,8 @@ export default {
<diff-discussions
v-if=
"line.right.discussions.length"
:discussions=
"line.right.discussions"
:line=
"line.right"
:help-page-path=
"helpPagePath"
/>
</div>
<diff-line-note-form
...
...
app/assets/javascripts/diffs/components/parallel_diff_view.vue
View file @
6d14d1a9
...
...
@@ -17,6 +17,11 @@ export default {
type
:
Array
,
required
:
true
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
mapGetters
(
'
diffs
'
,
[
'
commitId
'
]),
...
...
@@ -49,6 +54,7 @@ export default {
:line="line"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
/>
</
template
>
</tbody>
...
...
app/assets/javascripts/diffs/index.js
View file @
6d14d1a9
...
...
@@ -16,6 +16,7 @@ export default function initDiffsApp(store) {
return
{
endpoint
:
dataset
.
endpoint
,
projectPath
:
dataset
.
projectPath
,
helpPagePath
:
dataset
.
helpPagePath
,
currentUser
:
JSON
.
parse
(
dataset
.
currentUserData
)
||
{},
changesEmptyStateIllustration
:
dataset
.
changesEmptyStateIllustration
,
};
...
...
@@ -31,6 +32,7 @@ export default function initDiffsApp(store) {
endpoint
:
this
.
endpoint
,
currentUser
:
this
.
currentUser
,
projectPath
:
this
.
projectPath
,
helpPagePath
:
this
.
helpPagePath
,
shouldShow
:
this
.
activeTab
===
'
diffs
'
,
changesEmptyStateIllustration
:
this
.
changesEmptyStateIllustration
,
},
...
...
app/assets/javascripts/lib/utils/text_markdown.js
View file @
6d14d1a9
...
...
@@ -39,7 +39,14 @@ function blockTagText(text, textArea, blockTag, selected) {
}
}
function
moveCursor
({
textArea
,
tag
,
positionBetweenTags
,
removedLastNewLine
,
select
})
{
function
moveCursor
({
textArea
,
tag
,
cursorOffset
,
positionBetweenTags
,
removedLastNewLine
,
select
,
})
{
var
pos
;
if
(
!
textArea
.
setSelectionRange
)
{
return
;
...
...
@@ -61,11 +68,24 @@ function moveCursor({ textArea, tag, positionBetweenTags, removedLastNewLine, se
pos
-=
1
;
}
if
(
cursorOffset
)
{
pos
-=
cursorOffset
;
}
return
textArea
.
setSelectionRange
(
pos
,
pos
);
}
}
export
function
insertMarkdownText
({
textArea
,
text
,
tag
,
blockTag
,
selected
,
wrap
,
select
})
{
export
function
insertMarkdownText
({
textArea
,
text
,
tag
,
cursorOffset
,
blockTag
,
selected
,
wrap
,
select
,
})
{
var
textToInsert
,
selectedSplit
,
startChar
,
...
...
@@ -154,20 +174,30 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr
return
moveCursor
({
textArea
,
tag
:
tag
.
replace
(
textPlaceholder
,
selected
),
cursorOffset
,
positionBetweenTags
:
wrap
&&
selected
.
length
===
0
,
removedLastNewLine
,
select
,
});
}
function
updateText
({
textArea
,
tag
,
blockTag
,
wrap
,
select
})
{
function
updateText
({
textArea
,
tag
,
cursorOffset
,
blockTag
,
wrap
,
select
,
tagContent
})
{
var
$textArea
,
selected
,
text
;
$textArea
=
$
(
textArea
);
textArea
=
$textArea
.
get
(
0
);
text
=
$textArea
.
val
();
selected
=
selectedText
(
text
,
textArea
);
selected
=
selectedText
(
text
,
textArea
)
||
tagContent
;
$textArea
.
focus
();
return
insertMarkdownText
({
textArea
,
text
,
tag
,
blockTag
,
selected
,
wrap
,
select
});
return
insertMarkdownText
({
textArea
,
text
,
tag
,
cursorOffset
,
blockTag
,
selected
,
wrap
,
select
,
});
}
export
function
addMarkdownListeners
(
form
)
{
...
...
@@ -178,9 +208,11 @@ export function addMarkdownListeners(form) {
return
updateText
({
textArea
:
$this
.
closest
(
'
.md-area
'
).
find
(
'
textarea
'
),
tag
:
$this
.
data
(
'
mdTag
'
),
cursorOffset
:
$this
.
data
(
'
mdCursorOffset
'
),
blockTag
:
$this
.
data
(
'
mdBlock
'
),
wrap
:
!
$this
.
data
(
'
mdPrepend
'
),
select
:
$this
.
data
(
'
mdSelect
'
),
tagContent
:
$this
.
data
(
'
mdTagContent
'
).
toString
(),
});
});
}
...
...
app/assets/javascripts/mr_notes/index.js
View file @
6d14d1a9
...
...
@@ -33,6 +33,7 @@ export default function initMrNotes() {
noteableData
,
currentUserData
:
JSON
.
parse
(
notesDataset
.
currentUserData
),
notesData
:
JSON
.
parse
(
notesDataset
.
notesData
),
helpPagePath
:
notesDataset
.
helpPagePath
,
};
},
computed
:
{
...
...
@@ -71,6 +72,7 @@ export default function initMrNotes() {
notesData
:
this
.
notesData
,
userData
:
this
.
currentUserData
,
shouldShow
:
this
.
activeTab
===
'
show
'
,
helpPagePath
:
this
.
helpPagePath
,
},
});
},
...
...
app/assets/javascripts/notes/components/note_body.vue
View file @
6d14d1a9
<
script
>
import
{
mapActions
}
from
'
vuex
'
;
import
$
from
'
jquery
'
;
import
noteEditedText
from
'
./note_edited_text.vue
'
;
import
noteAwardsList
from
'
./note_awards_list.vue
'
;
import
noteAttachment
from
'
./note_attachment.vue
'
;
import
noteForm
from
'
./note_form.vue
'
;
import
autosave
from
'
../mixins/autosave
'
;
import
Suggestions
from
'
~/vue_shared/components/markdown/suggestions.vue
'
;
export
default
{
components
:
{
...
...
@@ -12,6 +14,7 @@ export default {
noteAwardsList
,
noteAttachment
,
noteForm
,
Suggestions
,
},
mixins
:
[
autosave
],
props
:
{
...
...
@@ -19,6 +22,11 @@ export default {
type
:
Object
,
required
:
true
,
},
line
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
canEdit
:
{
type
:
Boolean
,
required
:
true
,
...
...
@@ -28,11 +36,22 @@ export default {
required
:
false
,
default
:
false
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
noteBody
()
{
return
this
.
note
.
note
;
},
hasSuggestion
()
{
return
this
.
note
.
suggestions
&&
this
.
note
.
suggestions
.
length
;
},
lineType
()
{
return
this
.
line
?
this
.
line
.
type
:
null
;
},
},
mounted
()
{
this
.
renderGFM
();
...
...
@@ -53,6 +72,7 @@ export default {
}
},
methods
:
{
...
mapActions
([
'
submitSuggestion
'
]),
renderGFM
()
{
$
(
this
.
$refs
[
'
note-body
'
]).
renderGFM
();
},
...
...
@@ -62,19 +82,35 @@ export default {
formCancelHandler
(
shouldConfirm
,
isDirty
)
{
this
.
$emit
(
'
cancelForm
'
,
shouldConfirm
,
isDirty
);
},
applySuggestion
({
suggestionId
,
flashContainer
,
callback
})
{
const
{
discussion_id
:
discussionId
,
id
:
noteId
}
=
this
.
note
;
this
.
submitSuggestion
({
discussionId
,
noteId
,
suggestionId
,
flashContainer
,
callback
});
},
},
};
</
script
>
<
template
>
<div
ref=
"note-body"
:class=
"
{ 'js-task-list-container': canEdit }" class="note-body">
<div
class=
"note-text md"
v-html=
"note.note_html"
></div>
<suggestions
v-if=
"hasSuggestion && !isEditing"
:suggestions=
"note.suggestions"
:note-html=
"note.note_html"
:line-type=
"lineType"
:help-page-path=
"helpPagePath"
@
apply=
"applySuggestion"
/>
<div
v-else
class=
"note-text md"
v-html=
"note.note_html"
></div>
<note-form
v-if=
"isEditing"
ref=
"noteForm"
:is-editing=
"isEditing"
:note-body=
"noteBody"
:note-id=
"note.id"
:line=
"line"
:note=
"note"
:help-page-path=
"helpPagePath"
:markdown-version=
"note.cached_markdown_version"
@
handleFormUpdate=
"handleFormUpdate"
@
cancelForm=
"formCancelHandler"
...
...
app/assets/javascripts/notes/components/note_form.vue
View file @
6d14d1a9
<
script
>
import
{
mergeUrlParams
}
from
'
~/lib/utils/url_utility
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
eventHub
from
'
../event_hub
'
;
import
issueWarning
from
'
../../vue_shared/components/issue/issue_warning.vue
'
;
...
...
@@ -53,6 +54,21 @@ export default {
required
:
false
,
default
:
false
,
},
line
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
note
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
...
...
@@ -79,7 +95,8 @@ export default {
return
'
#
'
;
},
markdownPreviewPath
()
{
return
this
.
getNoteableDataByProp
(
'
preview_note_path
'
);
const
notable
=
this
.
getNoteableDataByProp
(
'
preview_note_path
'
);
return
mergeUrlParams
({
preview_suggestions
:
true
},
notable
);
},
markdownDocsPath
()
{
return
this
.
getNotesDataByProp
(
'
markdownDocsPath
'
);
...
...
@@ -93,6 +110,18 @@ export default {
isDisabled
()
{
return
!
this
.
updatedNoteBody
.
length
||
this
.
isSubmitting
;
},
discussionNote
()
{
const
discussionNote
=
this
.
discussion
.
id
?
this
.
getDiscussionLastNote
(
this
.
discussion
)
:
this
.
note
;
return
discussionNote
||
{};
},
canSuggest
()
{
return
(
this
.
getNoteableData
.
can_receive_suggestion
&&
(
this
.
line
&&
this
.
line
.
can_receive_suggestion
)
);
},
},
watch
:
{
noteBody
()
{
...
...
@@ -171,7 +200,11 @@ export default {
:markdown-docs-path=
"markdownDocsPath"
:markdown-version=
"markdownVersion"
:quick-actions-docs-path=
"quickActionsDocsPath"
:line=
"line"
:note=
"discussionNote"
:can-suggest=
"canSuggest"
:add-spacing-classes=
"false"
:help-page-path=
"helpPagePath"
>
<textarea
id=
"note_note"
...
...
app/assets/javascripts/notes/components/noteable_discussion.vue
View file @
6d14d1a9
...
...
@@ -49,6 +49,11 @@ export default {
type
:
Object
,
required
:
true
,
},
line
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
renderDiffFile
:
{
type
:
Boolean
,
required
:
false
,
...
...
@@ -64,6 +69,11 @@ export default {
required
:
false
,
default
:
false
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
const
{
diff_discussion
:
isDiffDiscussion
,
resolved
}
=
this
.
discussion
;
...
...
@@ -194,6 +204,13 @@ export default {
false
,
);
},
diffLine
()
{
if
(
this
.
discussion
.
diff_discussion
&&
this
.
discussion
.
truncated_diff_lines
)
{
return
this
.
discussion
.
truncated_diff_lines
.
slice
(
-
1
)[
0
];
}
return
this
.
line
;
},
},
watch
:
{
isReplying
()
{
...
...
@@ -357,6 +374,8 @@ Please check your network connection and try again.`;
<component
:is=
"componentName(initialDiscussion)"
:note=
"componentData(initialDiscussion)"
:line=
"line"
:help-page-path=
"helpPagePath"
@
handleDeleteNote=
"deleteNoteHandler"
>
<slot
slot=
"avatar-badge"
name=
"avatar-badge"
></slot>
...
...
@@ -373,6 +392,8 @@ Please check your network connection and try again.`;
v-for=
"note in replies"
:key=
"note.id"
:note=
"componentData(note)"
:help-page-path=
"helpPagePath"
:line=
"line"
@
handleDeleteNote=
"deleteNoteHandler"
/>
</
template
>
...
...
@@ -383,6 +404,8 @@ Please check your network connection and try again.`;
v-for=
"(note, index) in discussion.notes"
:key=
"note.id"
:note=
"componentData(note)"
:help-page-path=
"helpPagePath"
:line=
"diffLine"
@
handleDeleteNote=
"deleteNoteHandler"
>
<slot
v-if=
"index === 0"
slot=
"avatar-badge"
name=
"avatar-badge"
></slot>
...
...
@@ -447,6 +470,7 @@ Please check your network connection and try again.`;
ref=
"noteForm"
:discussion=
"discussion"
:is-editing=
"false"
:line=
"diffLine"
save-button-title=
"Comment"
@
handleFormUpdate=
"saveReply"
@
cancelForm=
"cancelReplyForm"
...
...
app/assets/javascripts/notes/components/noteable_note.vue
View file @
6d14d1a9
...
...
@@ -27,6 +27,16 @@ export default {
type
:
Object
,
required
:
true
,
},
line
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
helpPagePath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
...
...
@@ -220,8 +230,10 @@ export default {
<note-body
ref=
"noteBody"
:note=
"note"
:line=
"line"
:can-edit=
"note.current_user.can_edit"
:is-editing=
"isEditing"
:help-page-path=
"helpPagePath"
@
handleFormUpdate=
"formUpdateHandler"