gitlab-org--gitlab-foss/app/assets/javascripts/notes/components/note_form.vue

340 lines
10 KiB
Vue
Raw Normal View History

<script>
import { mergeUrlParams } from '~/lib/utils/url_utility';
2018-03-16 16:16:21 -04:00
import { mapGetters, mapActions } from 'vuex';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
2019-01-12 15:15:08 -05:00
import { __ } from '~/locale';
import { getDraft, updateDraft } from '~/lib/utils/autosave';
import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
2018-03-16 16:16:21 -04:00
export default {
name: 'NoteForm',
2018-03-16 16:16:21 -04:00
components: {
issueWarning,
markdownField,
},
mixins: [issuableStateMixin, resolvable, noteFormMixin],
2018-03-16 16:16:21 -04:00
props: {
noteBody: {
type: String,
required: false,
default: '',
2018-01-04 19:18:35 -05:00
},
2018-03-16 16:16:21 -04:00
noteId: {
type: [String, Number],
2018-03-16 16:16:21 -04:00
required: false,
default: '',
},
2018-03-16 16:16:21 -04:00
saveButtonTitle: {
type: String,
required: false,
2019-01-12 15:15:08 -05:00
default: __('Save comment'),
},
2018-06-21 08:22:40 -04:00
discussion: {
2018-03-16 16:16:21 -04:00
type: Object,
required: false,
default: () => ({}),
},
2018-03-16 16:16:21 -04:00
isEditing: {
type: Boolean,
required: true,
},
2018-06-21 08:22:40 -04:00
lineCode: {
type: String,
required: false,
default: '',
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
line: {
type: Object,
required: false,
default: null,
},
note: {
type: Object,
required: false,
default: null,
},
diffFile: {
type: Object,
required: false,
default: null,
},
helpPagePath: {
type: String,
required: false,
default: '',
},
autosaveKey: {
type: String,
required: false,
default: '',
},
2018-03-16 16:16:21 -04:00
},
data() {
let updatedNoteBody = this.noteBody;
if (!updatedNoteBody && this.autosaveKey) {
updatedNoteBody = getDraft(this.autosaveKey) || '';
}
2018-03-16 16:16:21 -04:00
return {
updatedNoteBody,
2018-03-16 16:16:21 -04:00
conflictWhileEditing: false,
isSubmitting: false,
isResolving: this.resolveDiscussion,
isUnresolving: !this.resolveDiscussion,
2018-03-16 16:16:21 -04:00
resolveAsThread: true,
};
},
computed: {
...mapGetters([
'getDiscussionLastNote',
'getNoteableData',
'getNoteableDataByProp',
'getNotesDataByProp',
'getUserDataByProp',
]),
noteHash() {
if (this.noteId) {
return `#note_${this.noteId}`;
}
return '#';
2018-03-16 16:16:21 -04:00
},
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-03-16 16:16:21 -04:00
markdownPreviewPath() {
const notable = this.getNoteableDataByProp('preview_note_path');
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-03-16 16:16:21 -04:00
},
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
},
quickActionsDocsPath() {
2018-06-21 08:22:40 -04:00
return !this.isEditing ? this.getNotesDataByProp('quickActionsDocsPath') : undefined;
2018-01-04 19:18:35 -05:00
},
2018-03-16 16:16:21 -04:00
currentUserId() {
return this.getUserDataByProp('id');
2018-01-04 19:18:35 -05:00
},
2018-03-16 16:16:21 -04:00
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)
);
},
2018-03-16 16:16:21 -04:00
},
watch: {
noteBody() {
if (this.updatedNoteBody === this.noteBody) {
this.updatedNoteBody = this.noteBody;
} else {
this.conflictWhileEditing = true;
}
},
},
mounted() {
this.$refs.textarea.focus();
},
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);
const shouldToggleState =
newResolvedStateAfterUpdate !== undefined &&
beforeSubmitDiscussionState !== newResolvedStateAfterUpdate;
return shouldResolve || shouldToggleState;
},
2018-03-16 16:16:21 -04:00
editMyLastNote() {
if (this.updatedNoteBody === '') {
2018-06-21 08:22:40 -04:00
const lastNoteInDiscussion = this.getDiscussionLastNote(this.discussion);
2018-03-16 16:16:21 -04:00
if (lastNoteInDiscussion) {
eventHub.$emit('enterEditMode', {
noteId: lastNoteInDiscussion.id,
});
}
2018-03-16 16:16:21 -04:00
}
},
cancelHandler(shouldConfirm = false) {
// Sends information about confirm message and if the textarea has changed
2018-06-21 08:22:40 -04:00
this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
},
onInput() {
if (this.autosaveKey) {
const { autosaveKey, updatedNoteBody: text } = this;
updateDraft(autosaveKey, text);
}
},
2018-03-16 16:16:21 -04:00
},
};
</script>
<template>
2018-11-16 15:07:38 -05:00
<div ref="editNoteForm" class="note-edit-form current-note-edit-form js-discussion-note-form">
<div v-if="conflictWhileEditing" class="js-conflict-edit-warning alert alert-danger">
This comment has changed since you started editing, please review the
<a :href="noteHash" target="_blank" rel="noopener noreferrer">updated comment</a> to ensure
2018-11-16 15:07:38 -05:00
information is not lost.
</div>
<div class="flash-container timeline-content"></div>
2018-11-16 15:07:38 -05:00
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
2017-09-14 07:01:07 -04:00
<issue-warning
2017-11-30 17:44:41 -05:00
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
2017-09-14 07:01:07 -04:00
/>
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:line="line"
:note="discussionNote"
:can-suggest="canSuggest"
2018-11-16 15:07:38 -05:00
:add-spacing-classes="false"
:help-page-path="helpPagePath"
2018-11-16 15:07:38 -05:00
>
<textarea
2017-08-12 20:00:22 -04:00
id="note_note"
2018-06-11 05:49:47 -04:00
ref="textarea"
slot="textarea"
v-model="updatedNoteBody"
:data-supports-quick-actions="!isEditing"
2017-07-18 03:25:20 -04:00
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
dir="auto"
aria-label="Description"
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>
</markdown-field>
<div class="note-form-actions clearfix">
<template v-if="showBatchCommentsActions">
<p v-if="showResolveDiscussionToggle">
<label>
<template v-if="discussionResolved">
<input
v-model="isUnresolving"
type="checkbox"
class="qa-unresolve-review-discussion"
/>
{{ __('Unresolve discussion') }}
</template>
<template v-else>
<input v-model="isResolving" type="checkbox" class="qa-resolve-review-discussion" />
{{ __('Resolve discussion') }}
</template>
</label>
</p>
<div>
<button
:disabled="isDisabled"
type="button"
class="btn btn-success qa-start-review"
@click="handleAddToReview"
>
<template v-if="hasDrafts">{{ __('Add to review') }}</template>
<template v-else>{{ __('Start a review') }}</template>
</button>
<button
:disabled="isDisabled"
type="button"
class="btn qa-comment-now"
@click="handleUpdate()"
>
{{ __('Add comment now') }}
</button>
<button
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button"
@click="cancelHandler()"
>
{{ __('Cancel') }}
</button>
</div>
</template>
<template v-else>
<button
:disabled="isDisabled"
type="button"
class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
@click="handleUpdate()"
>
{{ saveButtonTitle }}
</button>
<button
v-if="discussion.resolvable"
class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
@click.prevent="handleUpdate(true)"
>
{{ resolveButtonTitle }}
</button>
<button
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button"
@click="cancelHandler()"
>
Cancel
</button>
</template>
</div>
</form>
</div>
</template>