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

458 lines
14 KiB
Vue
Raw Normal View History

2018-03-17 18:26:18 +05:30
<script>
2020-11-24 15:15:51 +05:30
/* eslint-disable vue/no-v-html */
2021-03-11 19:13:27 +05:30
import { GlButton } from '@gitlab/ui';
2020-06-23 00:09:42 +05:30
import { mapGetters, mapActions, mapState } from 'vuex';
2021-03-11 19:13:27 +05:30
import { getDraft, updateDraft } from '~/lib/utils/autosave';
2020-01-01 13:55:28 +05:30
import { mergeUrlParams } from '~/lib/utils/url_utility';
2021-03-11 19:13:27 +05:30
import { __, sprintf } from '~/locale';
2021-03-08 18:12:59 +05:30
import markdownField from '~/vue_shared/components/markdown/field.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
2021-03-11 19:13:27 +05:30
import eventHub from '../event_hub';
2018-05-09 12:01:36 +05:30
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
2021-03-08 18:12:59 +05:30
import CommentFieldLayout from './comment_field_layout.vue';
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
export default {
2018-11-18 11:00:15 +05:30
name: 'NoteForm',
2018-05-09 12:01:36 +05:30
components: {
markdownField,
2021-03-08 18:12:59 +05:30
CommentFieldLayout,
2021-03-11 19:13:27 +05:30
GlButton,
2018-05-09 12:01:36 +05:30
},
2021-03-08 18:12:59 +05:30
mixins: [glFeatureFlagsMixin(), issuableStateMixin, resolvable],
2018-05-09 12:01:36 +05:30
props: {
noteBody: {
type: String,
required: false,
default: '',
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
noteId: {
2018-12-05 23:21:45 +05:30
type: [String, Number],
2018-05-09 12:01:36 +05:30
required: false,
2018-11-20 20:47:30 +05:30
default: '',
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
saveButtonTitle: {
type: String,
required: false,
2019-03-02 22:35:43 +05:30
default: __('Save comment'),
2018-03-17 18:26:18 +05:30
},
2018-11-08 19:23:39 +05:30
discussion: {
2018-05-09 12:01:36 +05:30
type: Object,
required: false,
default: () => ({}),
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
isEditing: {
type: Boolean,
required: true,
},
2018-11-08 19:23:39 +05:30
lineCode: {
type: String,
required: false,
default: '',
},
2019-02-15 15:39:39 +05:30
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
line: {
type: Object,
required: false,
default: null,
},
2021-04-29 21:17:54 +05:30
lines: {
type: Array,
required: false,
default: () => [],
},
2019-02-15 15:39:39 +05:30
note: {
type: Object,
required: false,
default: null,
},
2019-07-07 11:18:12 +05:30
diffFile: {
type: Object,
required: false,
default: null,
},
2019-02-15 15:39:39 +05:30
helpPagePath: {
type: String,
required: false,
default: '',
},
2019-07-07 11:18:12 +05:30
autosaveKey: {
type: String,
required: false,
default: '',
},
2019-09-04 21:01:54 +05:30
showSuggestPopover: {
type: Boolean,
required: false,
default: false,
},
2020-06-23 00:09:42 +05:30
isDraft: {
type: Boolean,
required: false,
default: false,
},
2018-05-09 12:01:36 +05:30
},
data() {
2019-07-07 11:18:12 +05:30
let updatedNoteBody = this.noteBody;
if (!updatedNoteBody && this.autosaveKey) {
updatedNoteBody = getDraft(this.autosaveKey) || '';
}
2018-05-09 12:01:36 +05:30
return {
2019-07-07 11:18:12 +05:30
updatedNoteBody,
2018-05-09 12:01:36 +05:30
conflictWhileEditing: false,
isSubmitting: false,
2019-02-15 15:39:39 +05:30
isResolving: this.resolveDiscussion,
isUnresolving: !this.resolveDiscussion,
2018-05-09 12:01:36 +05:30
resolveAsThread: true,
2020-07-28 23:09:34 +05:30
isSubmittingWithKeydown: false,
2018-05-09 12:01:36 +05:30
};
},
computed: {
...mapGetters([
'getDiscussionLastNote',
'getNoteableData',
'getNoteableDataByProp',
'getNotesDataByProp',
'getUserDataByProp',
]),
2020-06-23 00:09:42 +05:30
...mapState({
2021-03-08 18:12:59 +05:30
withBatchComments: (state) => state.batchComments?.withBatchComments,
2020-06-23 00:09:42 +05:30
}),
...mapGetters('batchComments', ['hasDrafts']),
showBatchCommentsActions() {
return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit;
},
showResolveDiscussionToggle() {
2021-01-29 00:20:46 +05:30
if (!this.discussion?.notes) return false;
return (
this.discussion?.notes
2021-03-08 18:12:59 +05:30
.filter((n) => n.resolvable)
.some((n) => n.current_user?.can_resolve_discussion) || this.isDraft
2021-01-29 00:20:46 +05:30
);
2020-06-23 00:09:42 +05:30
},
2018-05-09 12:01:36 +05:30
noteHash() {
2018-11-20 20:47:30 +05:30
if (this.noteId) {
return `#note_${this.noteId}`;
}
return '#';
2018-05-09 12:01:36 +05:30
},
2019-07-07 11:18:12 +05:30
diffParams() {
if (this.diffFile) {
return {
filePath: this.diffFile.file_path,
refs: this.diffFile.diff_refs,
};
} else if (this.note && this.note.position) {
return {
filePath: this.note.position.new_path,
refs: this.note.position,
};
} else if (this.discussion && this.discussion.diff_file) {
return {
filePath: this.discussion.diff_file.file_path,
refs: this.discussion.diff_file.diff_refs,
};
}
return null;
},
2018-05-09 12:01:36 +05:30
markdownPreviewPath() {
2019-02-15 15:39:39 +05:30
const notable = this.getNoteableDataByProp('preview_note_path');
2019-07-07 11:18:12 +05:30
const previewSuggestions = this.line && this.diffParams;
const params = previewSuggestions
? {
preview_suggestions: previewSuggestions,
line: this.line.new_line,
file_path: this.diffParams.filePath,
base_sha: this.diffParams.refs.base_sha,
start_sha: this.diffParams.refs.start_sha,
head_sha: this.diffParams.refs.head_sha,
}
: {};
return mergeUrlParams(params, notable);
2018-05-09 12:01:36 +05:30
},
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
},
quickActionsDocsPath() {
2018-11-08 19:23:39 +05:30
return !this.isEditing ? this.getNotesDataByProp('quickActionsDocsPath') : undefined;
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
currentUserId() {
return this.getUserDataByProp('id');
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
isDisabled() {
return !this.updatedNoteBody.length || this.isSubmitting;
},
2019-02-15 15:39:39 +05:30
discussionNote() {
const discussionNote = this.discussion.id
? this.getDiscussionLastNote(this.discussion)
: this.note;
return discussionNote || {};
},
canSuggest() {
return (
2021-03-08 18:12:59 +05:30
this.getNoteableData.can_receive_suggestion && this.line && this.line.can_receive_suggestion
2019-02-15 15:39:39 +05:30
);
},
2019-09-30 21:07:59 +05:30
changedCommentText() {
return sprintf(
__(
2021-04-17 20:07:23 +05:30
'This comment changed after you started editing it. Review the %{startTag}updated comment%{endTag} to ensure information is not lost.',
2019-09-30 21:07:59 +05:30
),
{
startTag: `<a href="${this.noteHash}" target="_blank" rel="noopener noreferrer">`,
endTag: '</a>',
},
false,
);
},
2018-05-09 12:01:36 +05:30
},
watch: {
noteBody() {
if (this.updatedNoteBody === this.noteBody) {
this.updatedNoteBody = this.noteBody;
} else {
this.conflictWhileEditing = true;
}
},
},
mounted() {
this.$refs.textarea.focus();
},
methods: {
...mapActions(['toggleResolveNote']),
2018-12-05 23:21:45 +05:30
shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) {
const newResolvedStateAfterUpdate =
this.shouldBeResolved && this.shouldBeResolved(shouldResolve);
const shouldToggleState =
newResolvedStateAfterUpdate !== undefined &&
beforeSubmitDiscussionState !== newResolvedStateAfterUpdate;
return shouldResolve || shouldToggleState;
},
2018-05-09 12:01:36 +05:30
editMyLastNote() {
if (this.updatedNoteBody === '') {
2018-11-08 19:23:39 +05:30
const lastNoteInDiscussion = this.getDiscussionLastNote(this.discussion);
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
if (lastNoteInDiscussion) {
eventHub.$emit('enterEditMode', {
noteId: lastNoteInDiscussion.id,
});
2018-03-17 18:26:18 +05:30
}
2018-05-09 12:01:36 +05:30
}
},
cancelHandler(shouldConfirm = false) {
// Sends information about confirm message and if the textarea has changed
2018-11-08 19:23:39 +05:30
this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
2018-03-17 18:26:18 +05:30
},
2019-07-07 11:18:12 +05:30
onInput() {
2020-07-28 23:09:34 +05:30
if (this.isSubmittingWithKeydown) {
return;
}
2019-07-07 11:18:12 +05:30
if (this.autosaveKey) {
const { autosaveKey, updatedNoteBody: text } = this;
updateDraft(autosaveKey, text);
}
},
2020-06-23 00:09:42 +05:30
handleKeySubmit() {
if (this.showBatchCommentsActions) {
this.handleAddToReview();
} else {
2020-07-28 23:09:34 +05:30
this.isSubmittingWithKeydown = true;
2020-06-23 00:09:42 +05:30
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);
},
2021-03-08 18:12:59 +05:30
hasEmailParticipants() {
return this.getNoteableData.issue_email_participants?.length;
},
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
<div ref="editNoteForm" class="note-edit-form current-note-edit-form js-discussion-note-form">
2019-09-30 21:07:59 +05:30
<div
v-if="conflictWhileEditing"
class="js-conflict-edit-warning alert alert-danger"
v-html="changedCommentText"
></div>
2018-03-17 18:26:18 +05:30
<div class="flash-container timeline-content"></div>
2019-02-15 15:39:39 +05:30
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
2021-03-08 18:12:59 +05:30
<comment-field-layout :noteable-data="getNoteableData">
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:line="line"
:note="discussionNote"
:can-suggest="canSuggest"
:add-spacing-classes="false"
:help-page-path="helpPagePath"
:show-suggest-popover="showSuggestPopover"
:textarea-value="updatedNoteBody"
2021-04-29 21:17:54 +05:30
:lines="lines"
2021-03-08 18:12:59 +05:30
@handleSuggestDismissed="() => $emit('handleSuggestDismissed')"
>
<template #textarea>
<textarea
id="note_note"
ref="textarea"
v-model="updatedNoteBody"
:data-supports-quick-actions="!isEditing && !glFeatures.tributeAutocomplete"
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form"
data-qa-selector="reply_field"
dir="auto"
2021-04-17 20:07:23 +05:30
:aria-label="__('Reply to comment')"
2021-03-08 18:12:59 +05:30
:placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="handleKeySubmit()"
@keydown.ctrl.enter="handleKeySubmit()"
@keydown.exact.up="editMyLastNote()"
@keydown.exact.esc="cancelHandler(true)"
@input="onInput"
></textarea>
</template>
</markdown-field>
</comment-field-layout>
2018-03-17 18:26:18 +05:30
<div class="note-form-actions clearfix">
2019-07-31 22:56:46 +05:30
<template v-if="showBatchCommentsActions">
<p v-if="showResolveDiscussionToggle">
<label>
<template v-if="discussionResolved">
<input
v-model="isUnresolving"
type="checkbox"
2020-06-23 00:09:42 +05:30
class="js-unresolve-checkbox"
2019-10-12 21:52:04 +05:30
data-qa-selector="unresolve_review_discussion_checkbox"
2019-07-31 22:56:46 +05:30
/>
2019-09-30 21:07:59 +05:30
{{ __('Unresolve thread') }}
2019-07-31 22:56:46 +05:30
</template>
<template v-else>
2019-10-12 21:52:04 +05:30
<input
v-model="isResolving"
type="checkbox"
2020-06-23 00:09:42 +05:30
class="js-resolve-checkbox"
2019-10-12 21:52:04 +05:30
data-qa-selector="resolve_review_discussion_checkbox"
/>
2019-09-30 21:07:59 +05:30
{{ __('Resolve thread') }}
2019-07-31 22:56:46 +05:30
</template>
</label>
</p>
2021-03-11 19:13:27 +05:30
<div class="gl-display-sm-flex gl-flex-wrap">
<gl-button
2019-07-31 22:56:46 +05:30
:disabled="isDisabled"
2021-03-11 19:13:27 +05:30
category="primary"
2021-04-29 21:17:54 +05:30
variant="confirm"
2021-03-11 19:13:27 +05:30
class="gl-mr-3"
2021-01-03 14:25:43 +05:30
data-qa-selector="start_review_button"
2019-07-31 22:56:46 +05:30
@click="handleAddToReview"
>
<template v-if="hasDrafts">{{ __('Add to review') }}</template>
<template v-else>{{ __('Start a review') }}</template>
2021-03-11 19:13:27 +05:30
</gl-button>
<gl-button
2019-07-31 22:56:46 +05:30
:disabled="isDisabled"
2021-03-11 19:13:27 +05:30
category="secondary"
variant="default"
2021-01-03 14:25:43 +05:30
data-qa-selector="comment_now_button"
2021-03-11 19:13:27 +05:30
class="gl-mr-3 js-comment-button"
2019-07-31 22:56:46 +05:30
@click="handleUpdate()"
>
{{ __('Add comment now') }}
2021-03-11 19:13:27 +05:30
</gl-button>
<gl-button
class="note-edit-cancel js-close-discussion-note-form"
category="secondary"
variant="default"
2020-05-24 23:13:21 +05:30
data-testid="cancelBatchCommentsEnabled"
@click="cancelHandler(true)"
2019-07-31 22:56:46 +05:30
>
{{ __('Cancel') }}
2021-03-11 19:13:27 +05:30
</gl-button>
2019-07-31 22:56:46 +05:30
</div>
</template>
<template v-else>
2021-03-11 19:13:27 +05:30
<div class="gl-display-sm-flex gl-flex-wrap">
<gl-button
:disabled="isDisabled"
category="primary"
2021-04-29 21:17:54 +05:30
variant="confirm"
2021-03-11 19:13:27 +05:30
data-qa-selector="reply_comment_button"
class="gl-mr-3 js-vue-issue-save js-comment-button"
@click="handleUpdate()"
>
{{ saveButtonTitle }}
</gl-button>
<gl-button
v-if="discussion.resolvable"
category="secondary"
variant="default"
class="gl-mr-3 js-comment-resolve-button"
@click.prevent="handleUpdate(true)"
>
{{ resolveButtonTitle }}
</gl-button>
<gl-button
class="note-edit-cancel js-close-discussion-note-form"
category="secondary"
variant="default"
data-testid="cancel"
@click="cancelHandler(true)"
>
{{ __('Cancel') }}
</gl-button>
</div>
2019-07-31 22:56:46 +05:30
</template>
2018-03-17 18:26:18 +05:30
</div>
</form>
</div>
</template>