debian-mirror-gitlab/app/assets/javascripts/notes/components/noteable_note.vue

404 lines
12 KiB
Vue
Raw Normal View History

2018-03-17 18:26:18 +05:30
<script>
2018-05-09 12:01:36 +05:30
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
2020-03-13 15:44:24 +05:30
import { escape } from 'lodash';
2020-06-23 00:09:42 +05:30
import { GlSprintf } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
2019-03-02 22:35:43 +05:30
import { truncateSha } from '~/lib/utils/text_utility';
2019-02-15 15:39:39 +05:30
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
2019-09-30 21:07:59 +05:30
import { __, s__, sprintf } from '../../locale';
2018-05-09 12:01:36 +05:30
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue';
2019-09-04 21:01:54 +05:30
import NoteBody from './note_body.vue';
2018-05-09 12:01:36 +05:30
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
2019-12-04 20:38:33 +05:30
import httpStatusCodes from '~/lib/utils/http_status';
2020-06-23 00:09:42 +05:30
import {
getStartLineNumber,
getEndLineNumber,
getLineClasses,
commentLineOptions,
} from './multiline_comment_utils';
import MultilineCommentForm from './multiline_comment_form.vue';
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
export default {
2018-11-08 19:23:39 +05:30
name: 'NoteableNote',
2018-05-09 12:01:36 +05:30
components: {
2020-06-23 00:09:42 +05:30
GlSprintf,
2018-05-09 12:01:36 +05:30
userAvatarLink,
noteHeader,
noteActions,
2019-09-04 21:01:54 +05:30
NoteBody,
2019-02-15 15:39:39 +05:30
TimelineEntryItem,
2020-06-23 00:09:42 +05:30
MultilineCommentForm,
2018-05-09 12:01:36 +05:30
},
2020-06-23 00:09:42 +05:30
mixins: [noteable, resolvable, glFeatureFlagsMixin()],
2018-05-09 12:01:36 +05:30
props: {
note: {
type: Object,
required: true,
2018-03-17 18:26:18 +05:30
},
2019-02-15 15:39:39 +05:30
line: {
type: Object,
required: false,
default: null,
},
helpPagePath: {
type: String,
required: false,
default: '',
},
2019-03-02 22:35:43 +05:30
commit: {
type: Object,
required: false,
default: () => null,
},
2019-07-07 11:18:12 +05:30
showReplyButton: {
type: Boolean,
required: false,
default: false,
},
2020-06-23 00:09:42 +05:30
diffLines: {
type: Object,
required: false,
default: null,
},
2018-05-09 12:01:36 +05:30
},
data() {
return {
isEditing: false,
isDeleting: false,
isRequesting: false,
isResolving: false,
2020-06-23 00:09:42 +05:30
commentLineStart: {
line_code: this.line?.line_code,
type: this.line?.type,
},
2018-05-09 12:01:36 +05:30
};
},
computed: {
2020-06-23 00:09:42 +05:30
...mapGetters('diffs', ['getDiffFileByHash']),
2019-03-02 22:35:43 +05:30
...mapGetters(['targetNoteHash', 'getNoteableData', 'getUserData', 'commentsDisabled']),
2018-05-09 12:01:36 +05:30
author() {
return this.note.author;
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
classNameBindings() {
2018-03-17 18:26:18 +05:30
return {
2018-11-08 19:23:39 +05:30
[`note-row-${this.note.id}`]: true,
2018-05-09 12:01:36 +05:30
'is-editing': this.isEditing && !this.isRequesting,
'is-requesting being-posted': this.isRequesting,
'disabled-content': this.isDeleting,
2018-11-08 19:23:39 +05:30
target: this.isTarget,
2019-02-15 15:39:39 +05:30
'is-editable': this.note.current_user.can_edit,
2018-03-17 18:26:18 +05:30
};
},
2018-05-09 12:01:36 +05:30
canReportAsAbuse() {
2019-09-04 21:01:54 +05:30
return Boolean(this.note.report_abuse_path) && this.author.id !== this.getUserData.id;
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
noteAnchorId() {
return `note_${this.note.id}`;
2018-03-17 18:26:18 +05:30
},
2018-11-08 19:23:39 +05:30
isTarget() {
return this.targetNoteHash === this.noteAnchorId;
},
2019-03-02 22:35:43 +05:30
discussionId() {
if (this.discussion) {
return this.discussion.id;
}
return '';
},
actionText() {
if (!this.commit) {
return '';
}
2019-07-07 11:18:12 +05:30
// We need to do this to ensure we have the correct sentence order
2019-03-02 22:35:43 +05:30
// when translating this as the sentence order may change from one
// language to the next. See:
2019-12-04 20:38:33 +05:30
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/24427#note_133713771
2019-03-02 22:35:43 +05:30
const { id, url } = this.commit;
const commitLink = `<a class="commit-sha monospace" href="${escape(url)}">${truncateSha(
id,
)}</a>`;
return sprintf(s__('MergeRequests|commented on commit %{commitLink}'), { commitLink }, false);
},
2020-06-23 00:09:42 +05:30
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);
},
2018-05-09 12:01:36 +05:30
},
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
created() {
eventHub.$on('enterEditMode', ({ noteId }) => {
if (noteId === this.note.id) {
2018-03-17 18:26:18 +05:30
this.isEditing = true;
2018-05-09 12:01:36 +05:30
this.scrollToNoteIfNeeded($(this.$el));
}
});
},
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
mounted() {
if (this.isTarget) {
this.scrollToNoteIfNeeded($(this.$el));
}
},
2018-05-09 12:01:36 +05:30
methods: {
2019-12-04 20:38:33 +05:30
...mapActions([
'deleteNote',
'removeNote',
'updateNote',
'toggleResolveNote',
'scrollToNoteIfNeeded',
2020-06-23 00:09:42 +05:30
'updateAssignees',
2019-12-04 20:38:33 +05:30
]),
2018-05-09 12:01:36 +05:30
editHandler() {
this.isEditing = true;
2018-12-05 23:21:45 +05:30
this.$emit('handleEdit');
2018-05-09 12:01:36 +05:30
},
deleteHandler() {
2019-09-30 21:07:59 +05:30
const typeOfComment = this.note.isDraft ? __('pending comment') : __('comment');
if (
// eslint-disable-next-line no-alert
window.confirm(
sprintf(__('Are you sure you want to delete this %{typeOfComment}?'), { typeOfComment }),
)
) {
2018-05-09 12:01:36 +05:30
this.isDeleting = true;
2018-11-20 20:47:30 +05:30
this.$emit('handleDeleteNote', this.note);
2018-03-17 18:26:18 +05:30
2018-12-05 23:21:45 +05:30
if (this.note.isDraft) return;
2018-05-09 12:01:36 +05:30
this.deleteNote(this.note)
2018-03-17 18:26:18 +05:30
.then(() => {
2018-05-09 12:01:36 +05:30
this.isDeleting = false;
2018-03-17 18:26:18 +05:30
})
.catch(() => {
2019-09-30 21:07:59 +05:30
Flash(__('Something went wrong while deleting your note. Please try again.'));
2018-05-09 12:01:36 +05:30
this.isDeleting = false;
2018-03-17 18:26:18 +05:30
});
2018-05-09 12:01:36 +05:30
}
},
2018-12-05 23:21:45 +05:30
updateSuccess() {
this.isEditing = false;
this.isRequesting = false;
this.oldContent = null;
$(this.$refs.noteBody.$el).renderGFM();
this.$refs.noteBody.resetAutoSave();
this.$emit('updateSuccess');
},
2019-07-31 22:56:46 +05:30
formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) {
2020-06-23 00:09:42 +05:30
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,
},
};
2018-12-05 23:21:45 +05:30
this.$emit('handleUpdateNote', {
note: this.note,
noteText,
2019-07-31 22:56:46 +05:30
resolveDiscussion,
2020-06-23 00:09:42 +05:30
position,
2018-12-05 23:21:45 +05:30
callback: () => this.updateSuccess(),
});
2019-07-31 22:56:46 +05:30
if (this.isDraft) return;
2018-05-09 12:01:36 +05:30
const data = {
endpoint: this.note.path,
note: {
2018-11-08 19:23:39 +05:30
target_type: this.getNoteableData.targetType,
2018-05-09 12:01:36 +05:30
target_id: this.note.noteable_id,
note: { note: noteText },
},
};
this.isRequesting = true;
this.oldContent = this.note.note_html;
this.note.note_html = escape(noteText);
this.updateNote(data)
.then(() => {
2018-12-05 23:21:45 +05:30
this.updateSuccess();
2018-05-09 12:01:36 +05:30
callback();
})
2019-12-04 20:38:33 +05:30
.catch(response => {
if (response.status === httpStatusCodes.GONE) {
this.removeNote(this.note);
this.updateSuccess();
2018-05-09 12:01:36 +05:30
callback();
2019-12-04 20:38:33 +05:30
} else {
this.isRequesting = false;
this.isEditing = true;
this.$nextTick(() => {
const msg = __('Something went wrong while editing your comment. Please try again.');
Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText);
callback();
});
}
2018-05-09 12:01:36 +05:30
});
},
formCancelHandler(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
// eslint-disable-next-line no-alert
2019-09-30 21:07:59 +05:30
if (!window.confirm(__('Are you sure you want to cancel editing this comment?'))) return;
2018-05-09 12:01:36 +05:30
}
this.$refs.noteBody.resetAutoSave();
if (this.oldContent) {
this.note.note_html = this.oldContent;
this.oldContent = null;
}
this.isEditing = false;
2018-12-05 23:21:45 +05:30
this.$emit('cancelForm');
2018-05-09 12:01:36 +05:30
},
recoverNoteContent(noteText) {
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this.note.note = noteText;
2019-09-04 21:01:54 +05:30
const { noteBody } = this.$refs;
if (noteBody) {
noteBody.note.note = noteText;
}
2018-03-17 18:26:18 +05:30
},
2020-06-23 00:09:42 +05:30
getLineClasses(lineNumber) {
return getLineClasses(lineNumber);
},
assigneesUpdate(assignees) {
this.updateAssignees(assignees);
},
2018-05-09 12:01:36 +05:30
},
};
2018-03-17 18:26:18 +05:30
</script>
<template>
2019-02-15 15:39:39 +05:30
<timeline-entry-item
2018-03-17 18:26:18 +05:30
:id="noteAnchorId"
:class="classNameBindings"
2018-11-08 19:23:39 +05:30
:data-award-url="note.toggle_award_path"
:data-note-id="note.id"
2019-07-07 11:18:12 +05:30
class="note note-wrapper qa-noteable-note-item"
2018-11-08 19:23:39 +05:30
>
2020-06-23 00:09:42 +05:30
<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>
2019-02-15 15:39:39 +05:30
<div v-once class="timeline-icon">
<user-avatar-link
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
>
<slot slot="avatar-badge" name="avatar-badge"></slot>
</user-avatar-link>
</div>
<div class="timeline-content">
<div class="note-header">
2020-05-24 23:13:21 +05:30
<note-header
v-once
:author="author"
:created-at="note.created_at"
:note-id="note.id"
:is-confidential="note.confidential"
>
2019-07-31 22:56:46 +05:30
<slot slot="note-header-info" name="note-header-info"></slot>
2019-03-02 22:35:43 +05:30
<span v-if="commit" v-html="actionText"></span>
2020-05-24 23:13:21 +05:30
<span v-else-if="note.created_at" class="d-none d-sm-inline">&middot;</span>
2019-03-02 22:35:43 +05:30
</note-header>
2019-02-15 15:39:39 +05:30
<note-actions
2020-06-23 00:09:42 +05:30
:author="author"
2019-02-15 15:39:39 +05:30
:author-id="author.id"
:note-id="note.id"
:note-url="note.noteable_note_url"
:access-level="note.human_access"
2019-03-02 22:35:43 +05:30
:show-reply="showReplyButton"
2019-02-15 15:39:39 +05:30
:can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
2019-07-31 22:56:46 +05:30
:can-resolve="canResolve"
2019-02-15 15:39:39 +05:30
:report-abuse-path="note.report_abuse_path"
2019-07-31 22:56:46 +05:30
:resolvable="note.resolvable || note.isDraft"
:is-resolved="note.resolved || note.resolve_discussion"
2019-02-15 15:39:39 +05:30
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
2019-07-31 22:56:46 +05:30
:is-draft="note.isDraft"
:resolve-discussion="note.isDraft && note.resolve_discussion"
:discussion-id="discussionId"
2019-02-15 15:39:39 +05:30
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
2019-07-07 11:18:12 +05:30
@startReplying="$emit('startReplying')"
2020-06-23 00:09:42 +05:30
@updateAssignees="assigneesUpdate"
2019-02-15 15:39:39 +05:30
/>
2019-01-03 12:48:30 +05:30
</div>
2019-02-15 15:39:39 +05:30
<div class="timeline-discussion-body">
<slot name="discussion-resolved-text"></slot>
2019-01-03 12:48:30 +05:30
<note-body
ref="noteBody"
:note="note"
2019-02-15 15:39:39 +05:30
:line="line"
2018-03-17 18:26:18 +05:30
:can-edit="note.current_user.can_edit"
2019-01-03 12:48:30 +05:30
:is-editing="isEditing"
2019-02-15 15:39:39 +05:30
:help-page-path="helpPagePath"
2019-01-03 12:48:30 +05:30
@handleFormUpdate="formUpdateHandler"
@cancelForm="formCancelHandler"
2018-03-17 18:26:18 +05:30
/>
</div>
</div>
2019-02-15 15:39:39 +05:30
</timeline-entry-item>
2018-03-17 18:26:18 +05:30
</template>