Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/assets/javascripts/notes/components
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/assets/javascripts/notes/components')
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue11
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue1
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_form.vue68
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_utils.js57
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue65
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue36
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue70
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue5
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue98
9 files changed, 392 insertions, 19 deletions
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index a070cf8866a..16dcde46262 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -29,6 +29,7 @@ export default {
name: 'CommentForm',
components: {
issueWarning,
+ epicWarning: () => import('ee_component/vue_shared/components/epic/epic_warning.vue'),
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
@@ -60,6 +61,7 @@ export default {
'getCurrentUserLastNote',
'getUserData',
'getNoteableData',
+ 'getNoteableDataByProp',
'getNotesData',
'openState',
'getBlockedByIssues',
@@ -135,6 +137,9 @@ export default {
? __('merge request')
: __('issue');
},
+ isIssueType() {
+ return this.noteableDisplayName === constants.ISSUE_NOTEABLE_TYPE;
+ },
trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
},
@@ -346,13 +351,13 @@ export default {
<div class="error-alert"></div>
<issue-warning
- v-if="hasWarning(getNoteableData)"
+ v-if="hasWarning(getNoteableData) && isIssueType"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
:locked-issue-docs-path="lockedIssueDocsPath"
:confidential-issue-docs-path="confidentialIssueDocsPath"
/>
-
+ <epic-warning :is-confidential="isConfidential(getNoteableData)" />
<markdown-field
ref="markdownField"
:is-submitting="isSubmitting"
@@ -412,7 +417,7 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
</gl-alert>
<div class="note-form-actions">
<div
- class="float-left btn-group
+ class="btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<button
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index cd5cfc09ea0..8897b54fac7 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -116,6 +116,7 @@ export default {
</div>
<div v-else>
<diff-viewer
+ :diff-file="discussion.diff_file"
:diff-mode="diffMode"
:diff-viewer-mode="diffViewerMode"
:new-path="discussion.diff_file.new_path"
diff --git a/app/assets/javascripts/notes/components/multiline_comment_form.vue b/app/assets/javascripts/notes/components/multiline_comment_form.vue
new file mode 100644
index 00000000000..5fba011a153
--- /dev/null
+++ b/app/assets/javascripts/notes/components/multiline_comment_form.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlFormSelect, GlSprintf } from '@gitlab/ui';
+import { getSymbol, getLineClasses } from './multiline_comment_utils';
+
+export default {
+ components: { GlFormSelect, GlSprintf },
+ props: {
+ lineRange: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ line: {
+ type: Object,
+ required: true,
+ },
+ commentLineOptions: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ commentLineStart: {
+ lineCode: this.lineRange ? this.lineRange.start_line_code : this.line.line_code,
+ type: this.lineRange ? this.lineRange.start_line_type : this.line.type,
+ },
+ };
+ },
+ methods: {
+ getSymbol({ type }) {
+ return getSymbol(type);
+ },
+ getLineClasses(line) {
+ return getLineClasses(line);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-sprintf
+ :message="
+ s__('MergeRequestDiffs|Commenting on lines %{selectStart}start%{selectEnd} to %{end}')
+ "
+ >
+ <template #select>
+ <label for="comment-line-start" class="sr-only">{{
+ s__('MergeRequestDiffs|Select comment starting line')
+ }}</label>
+ <gl-form-select
+ id="comment-line-start"
+ :value="commentLineStart"
+ :options="commentLineOptions"
+ size="sm"
+ class="gl-w-auto gl-vertical-align-baseline"
+ @change="$emit('input', $event)"
+ />
+ </template>
+ <template #end>
+ <span :class="getLineClasses(line)">
+ {{ getSymbol(line) + (line.new_line || line.old_line) }}
+ </span>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/multiline_comment_utils.js b/app/assets/javascripts/notes/components/multiline_comment_utils.js
new file mode 100644
index 00000000000..dc9c55e9b30
--- /dev/null
+++ b/app/assets/javascripts/notes/components/multiline_comment_utils.js
@@ -0,0 +1,57 @@
+import { takeRightWhile } from 'lodash';
+
+export function getSymbol(type) {
+ if (type === 'new') return '+';
+ if (type === 'old') return '-';
+ return '';
+}
+
+function getLineNumber(lineRange, key) {
+ if (!lineRange || !key) return '';
+ const lineCode = lineRange[`${key}_line_code`] || '';
+ const lineType = lineRange[`${key}_line_type`] || '';
+ const lines = lineCode.split('_') || [];
+ const lineNumber = lineType === 'old' ? lines[1] : lines[2];
+ return (lineNumber && getSymbol(lineType) + lineNumber) || '';
+}
+
+export function getStartLineNumber(lineRange) {
+ return getLineNumber(lineRange, 'start');
+}
+
+export function getEndLineNumber(lineRange) {
+ return getLineNumber(lineRange, 'end');
+}
+
+export function getLineClasses(line) {
+ const symbol = typeof line === 'string' ? line.charAt(0) : getSymbol(line?.type);
+
+ if (symbol !== '+' && symbol !== '-') return '';
+
+ return [
+ 'gl-px-1 gl-rounded-small gl-border-solid gl-border-1 gl-border-white',
+ {
+ 'gl-bg-green-100 gl-text-green-800': symbol === '+',
+ 'gl-bg-red-100 gl-text-red-800': symbol === '-',
+ },
+ ];
+}
+
+export function commentLineOptions(diffLines, lineCode) {
+ const selectedIndex = diffLines.findIndex(line => line.line_code === lineCode);
+ const notMatchType = l => l.type !== 'match';
+
+ // We're limiting adding comments to only lines above the current line
+ // to make rendering simpler. Future interations will use a more
+ // intuitive dragging interface that will make this unnecessary
+ const upToSelected = diffLines.slice(0, selectedIndex + 1);
+
+ // Only include the lines up to the first "Show unchanged lines" block
+ // i.e. not a "match" type
+ const lines = takeRightWhile(upToSelected, notMatchType);
+
+ return lines.map(l => ({
+ value: { lineCode: l.line_code, type: l.type },
+ text: `${getSymbol(l.type)}${l.new_line || l.old_line}`,
+ }));
+}
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index dc514f00801..f1af8be590a 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,9 +1,13 @@
<script>
+import { __ } from '~/locale';
import { mapGetters } from 'vuex';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
-import resolvedStatusMixin from 'ee_else_ce/batch_comments/mixins/resolved_status';
+import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import Icon from '~/vue_shared/components/icon.vue';
import ReplyButton from './note_actions/reply_button.vue';
+import eventHub from '~/sidebar/event_hub';
+import Api from '~/api';
+import flash from '~/flash';
export default {
name: 'NoteActions',
@@ -17,6 +21,10 @@ export default {
},
mixins: [resolvedStatusMixin],
props: {
+ author: {
+ type: Object,
+ required: true,
+ },
authorId: {
type: Number,
required: true,
@@ -87,7 +95,7 @@ export default {
},
},
computed: {
- ...mapGetters(['getUserDataByProp']),
+ ...mapGetters(['getUserDataByProp', 'getNoteableData']),
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
@@ -100,6 +108,26 @@ export default {
currentUserId() {
return this.getUserDataByProp('id');
},
+ isUserAssigned() {
+ return this.assignees && this.assignees.some(({ id }) => id === this.author.id);
+ },
+ displayAssignUserText() {
+ return this.isUserAssigned
+ ? __('Unassign from commenting user')
+ : __('Assign to commenting user');
+ },
+ sidebarAction() {
+ return this.isUserAssigned ? 'sidebar.addAssignee' : 'sidebar.removeAssignee';
+ },
+ targetType() {
+ return this.getNoteableData.targetType;
+ },
+ assignees() {
+ return this.getNoteableData.assignees || [];
+ },
+ isIssue() {
+ return this.targetType === 'issue';
+ },
},
methods: {
onEdit() {
@@ -116,6 +144,29 @@ export default {
this.$root.$emit('bv::hide::tooltip');
});
},
+ handleAssigneeUpdate(assignees) {
+ this.$emit('updateAssignees', assignees);
+ eventHub.$emit(this.sidebarAction, this.author);
+ eventHub.$emit('sidebar.saveAssignees');
+ },
+ assignUser() {
+ let { assignees } = this;
+ const { project_id, iid } = this.getNoteableData;
+
+ if (this.isUserAssigned) {
+ assignees = assignees.filter(assignee => assignee.id !== this.author.id);
+ } else {
+ assignees.push({ id: this.author.id });
+ }
+
+ if (this.targetType === 'issue') {
+ Api.updateIssue(project_id, iid, {
+ assignee_ids: assignees.map(assignee => assignee.id),
+ })
+ .then(() => this.handleAssigneeUpdate(assignees))
+ .catch(() => flash(__('Something went wrong while updating assignees')));
+ }
+ },
},
};
</script>
@@ -215,6 +266,16 @@ export default {
<span class="text-danger">{{ __('Delete comment') }}</span>
</button>
</li>
+ <li v-if="isIssue">
+ <button
+ class="btn-default btn-transparent"
+ data-testid="assign-user"
+ type="button"
+ @click="assignUser"
+ >
+ {{ displayAssignUserText }}
+ </button>
+ </li>
</ul>
</div>
</div>
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index 358f49deb35..42b78929f8a 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,8 +1,7 @@
<script>
-import { mapActions } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
-import getDiscussion from 'ee_else_ce/notes/mixins/get_discussion';
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
@@ -18,7 +17,7 @@ export default {
noteForm,
Suggestions,
},
- mixins: [autosave, getDiscussion],
+ mixins: [autosave],
props: {
note: {
type: Object,
@@ -45,6 +44,15 @@ export default {
},
},
computed: {
+ ...mapGetters(['getDiscussion']),
+ discussion() {
+ if (!this.note.isDraft) return {};
+
+ return this.getDiscussion(this.note.discussion_id);
+ },
+ ...mapState({
+ batchSuggestionsInfo: state => state.notes.batchSuggestionsInfo,
+ }),
noteBody() {
return this.note.note;
},
@@ -74,7 +82,12 @@ export default {
}
},
methods: {
- ...mapActions(['submitSuggestion']),
+ ...mapActions([
+ 'submitSuggestion',
+ 'submitSuggestionBatch',
+ 'addSuggestionInfoToBatch',
+ 'removeSuggestionInfoFromBatch',
+ ]),
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
@@ -91,6 +104,17 @@ export default {
callback,
);
},
+ applySuggestionBatch({ flashContainer }) {
+ return this.submitSuggestionBatch({ flashContainer });
+ },
+ addSuggestionToBatch(suggestionId) {
+ const { discussion_id: discussionId, id: noteId } = this.note;
+
+ this.addSuggestionInfoToBatch({ suggestionId, discussionId, noteId });
+ },
+ removeSuggestionFromBatch(suggestionId) {
+ this.removeSuggestionInfoFromBatch(suggestionId);
+ },
},
};
</script>
@@ -100,10 +124,14 @@ export default {
<suggestions
v-if="hasSuggestion && !isEditing"
:suggestions="note.suggestions"
+ :batch-suggestions-info="batchSuggestionsInfo"
:note-html="note.note_html"
:line-type="lineType"
:help-page-path="helpPagePath"
@apply="applySuggestion"
+ @applyBatch="applySuggestionBatch"
+ @addToBatch="addSuggestionToBatch"
+ @removeFromBatch="removeSuggestionFromBatch"
/>
<div v-else class="note-text md" v-html="note.note_html"></div>
<note-form
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 21d0bffdf1c..795ee10ca0f 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,6 +1,5 @@
<script>
-import { mapGetters, mapActions } from 'vuex';
-import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
+import { mapGetters, mapActions, mapState } from 'vuex';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@@ -16,7 +15,7 @@ export default {
issueWarning,
markdownField,
},
- mixins: [issuableStateMixin, resolvable, noteFormMixin],
+ mixins: [issuableStateMixin, resolvable],
props: {
noteBody: {
type: String,
@@ -82,6 +81,11 @@ export default {
required: false,
default: false,
},
+ isDraft: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
let updatedNoteBody = this.noteBody;
@@ -107,6 +111,16 @@ export default {
'getNotesDataByProp',
'getUserDataByProp',
]),
+ ...mapState({
+ withBatchComments: state => state.batchComments?.withBatchComments,
+ }),
+ ...mapGetters('batchComments', ['hasDrafts']),
+ showBatchCommentsActions() {
+ return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit;
+ },
+ showResolveDiscussionToggle() {
+ return (this.discussion?.id && this.discussion.resolvable) || this.isDraft;
+ },
noteHash() {
if (this.noteId) {
return `#note_${this.noteId}`;
@@ -202,8 +216,6 @@ export default {
methods: {
...mapActions(['toggleResolveNote']),
shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) {
- // shouldBeResolved() checks the actual resolution state,
- // considering batchComments (EEP), if applicable/enabled.
const newResolvedStateAfterUpdate =
this.shouldBeResolved && this.shouldBeResolved(shouldResolve);
@@ -234,6 +246,50 @@ export default {
updateDraft(autosaveKey, text);
}
},
+ handleKeySubmit() {
+ if (this.showBatchCommentsActions) {
+ this.handleAddToReview();
+ } else {
+ this.handleUpdate();
+ }
+ },
+ handleUpdate(shouldResolve) {
+ const beforeSubmitDiscussionState = this.discussionResolved;
+ this.isSubmitting = true;
+
+ this.$emit(
+ 'handleFormUpdate',
+ this.updatedNoteBody,
+ this.$refs.editNoteForm,
+ () => {
+ this.isSubmitting = false;
+
+ if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
+ this.resolveHandler(beforeSubmitDiscussionState);
+ }
+ },
+ this.discussionResolved ? !this.isUnresolving : this.isResolving,
+ );
+ },
+ shouldBeResolved(resolveStatus) {
+ if (this.withBatchComments) {
+ return (
+ (this.discussionResolved && !this.isUnresolving) ||
+ (!this.discussionResolved && this.isResolving)
+ );
+ }
+
+ return resolveStatus;
+ },
+ handleAddToReview() {
+ // check if draft should resolve thread
+ const shouldResolve =
+ (this.discussionResolved && !this.isUnresolving) ||
+ (!this.discussionResolved && this.isResolving);
+ this.isSubmitting = true;
+
+ this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve);
+ },
},
};
</script>
@@ -293,6 +349,7 @@ export default {
<input
v-model="isUnresolving"
type="checkbox"
+ class="js-unresolve-checkbox"
data-qa-selector="unresolve_review_discussion_checkbox"
/>
{{ __('Unresolve thread') }}
@@ -301,6 +358,7 @@ export default {
<input
v-model="isResolving"
type="checkbox"
+ class="js-resolve-checkbox"
data-qa-selector="resolve_review_discussion_checkbox"
/>
{{ __('Resolve thread') }}
@@ -320,7 +378,7 @@ export default {
<button
:disabled="isDisabled"
type="button"
- class="btn qa-comment-now"
+ class="btn qa-comment-now js-comment-button"
@click="handleUpdate()"
>
{{ __('Add comment now') }}
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 189ff88feb3..7fe50d36c0c 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,11 +1,12 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
-import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
+import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { s__, __ } from '~/locale';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import DraftNote from '~/batch_comments/components/draft_note.vue';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import diffDiscussionHeader from './diff_discussion_header.vue';
@@ -26,7 +27,7 @@ export default {
diffDiscussionHeader,
noteSignedOutWidget,
noteForm,
- DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
+ DraftNote,
TimelineEntryItem,
DiscussionNotes,
DiscussionActions,
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 37675e20b3d..0e4dd1b9c84 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -2,7 +2,8 @@
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'lodash';
-import draftMixin from 'ee_else_ce/notes/mixins/draft';
+import { GlSprintf } from '@gitlab/ui';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import { __, s__, sprintf } from '../../locale';
@@ -15,17 +16,26 @@ import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import httpStatusCodes from '~/lib/utils/http_status';
+import {
+ getStartLineNumber,
+ getEndLineNumber,
+ getLineClasses,
+ commentLineOptions,
+} from './multiline_comment_utils';
+import MultilineCommentForm from './multiline_comment_form.vue';
export default {
name: 'NoteableNote',
components: {
+ GlSprintf,
userAvatarLink,
noteHeader,
noteActions,
NoteBody,
TimelineEntryItem,
+ MultilineCommentForm,
},
- mixins: [noteable, resolvable, draftMixin],
+ mixins: [noteable, resolvable, glFeatureFlagsMixin()],
props: {
note: {
type: Object,
@@ -51,6 +61,11 @@ export default {
required: false,
default: false,
},
+ diffLines: {
+ type: Object,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -58,9 +73,14 @@ export default {
isDeleting: false,
isRequesting: false,
isResolving: false,
+ commentLineStart: {
+ line_code: this.line?.line_code,
+ type: this.line?.type,
+ },
};
},
computed: {
+ ...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters(['targetNoteHash', 'getNoteableData', 'getUserData', 'commentsDisabled']),
author() {
return this.note.author;
@@ -105,6 +125,41 @@ export default {
)}</a>`;
return sprintf(s__('MergeRequests|commented on commit %{commitLink}'), { commitLink }, false);
},
+ isDraft() {
+ return this.note.isDraft;
+ },
+ canResolve() {
+ return (
+ this.note.current_user.can_resolve ||
+ (this.note.isDraft && this.note.discussion_id !== null)
+ );
+ },
+ lineRange() {
+ return this.note.position?.line_range;
+ },
+ startLineNumber() {
+ return getStartLineNumber(this.lineRange);
+ },
+ endLineNumber() {
+ return getEndLineNumber(this.lineRange);
+ },
+ showMultiLineComment() {
+ return (
+ this.glFeatures.multilineComments &&
+ this.startLineNumber &&
+ this.endLineNumber &&
+ (this.startLineNumber !== this.endLineNumber || this.isEditing)
+ );
+ },
+ commentLineOptions() {
+ if (this.diffLines) {
+ return commentLineOptions(this.diffLines, this.line.line_code);
+ }
+
+ const diffFile = this.diffFile || this.getDiffFileByHash(this.targetNoteHash);
+ if (!diffFile) return null;
+ return commentLineOptions(diffFile.highlighted_diff_lines, this.line.line_code);
+ },
},
created() {
@@ -129,6 +184,7 @@ export default {
'updateNote',
'toggleResolveNote',
'scrollToNoteIfNeeded',
+ 'updateAssignees',
]),
editHandler() {
this.isEditing = true;
@@ -166,10 +222,20 @@ export default {
this.$emit('updateSuccess');
},
formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) {
+ const position = {
+ ...this.note.position,
+ line_range: {
+ start_line_code: this.commentLineStart?.lineCode,
+ start_line_type: this.commentLineStart?.type,
+ end_line_code: this.line?.line_code,
+ end_line_type: this.line?.type,
+ },
+ };
this.$emit('handleUpdateNote', {
note: this.note,
noteText,
resolveDiscussion,
+ position,
callback: () => this.updateSuccess(),
});
@@ -231,6 +297,12 @@ export default {
noteBody.note.note = noteText;
}
},
+ getLineClasses(lineNumber) {
+ return getLineClasses(lineNumber);
+ },
+ assigneesUpdate(assignees) {
+ this.updateAssignees(assignees);
+ },
},
};
</script>
@@ -243,6 +315,26 @@ export default {
:data-note-id="note.id"
class="note note-wrapper qa-noteable-note-item"
>
+ <div v-if="showMultiLineComment" data-testid="multiline-comment">
+ <multiline-comment-form
+ v-if="isEditing && commentLineOptions && line"
+ v-model="commentLineStart"
+ :line="line"
+ :comment-line-options="commentLineOptions"
+ :line-range="note.position.line_range"
+ class="gl-mb-3 gl-text-gray-700 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-pb-3"
+ />
+ <div v-else class="gl-mb-3 gl-text-gray-700">
+ <gl-sprintf :message="__('Comment on lines %{startLine} to %{endLine}')">
+ <template #startLine>
+ <span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span>
+ </template>
+ <template #endLine>
+ <span :class="getLineClasses(endLineNumber)">{{ endLineNumber }}</span>
+ </template>
+ </gl-sprintf>
+ </div>
+ </div>
<div v-once class="timeline-icon">
<user-avatar-link
:link-href="author.path"
@@ -267,6 +359,7 @@ export default {
<span v-else-if="note.created_at" class="d-none d-sm-inline">&middot;</span>
</note-header>
<note-actions
+ :author="author"
:author-id="author.id"
:note-id="note.id"
:note-url="note.noteable_note_url"
@@ -289,6 +382,7 @@ export default {
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
@startReplying="$emit('startReplying')"
+ @updateAssignees="assigneesUpdate"
/>
</div>
<div class="timeline-discussion-body">