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

439 lines
14 KiB
Vue
Raw Normal View History

2018-03-17 18:26:18 +05:30
<script>
2018-05-09 12:01:36 +05:30
import { mapActions, mapGetters } from 'vuex';
import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionsSvg from 'icons/_next_discussion.svg';
2018-11-18 11:00:15 +05:30
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
2018-11-08 19:23:39 +05:30
import { truncateSha } from '~/lib/utils/text_utility';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
2018-11-18 11:00:15 +05:30
import { s__ } from '~/locale';
2018-05-09 12:01:36 +05:30
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
2018-11-18 11:00:15 +05:30
import discussionNavigation from '../mixins/discussion_navigation';
2018-05-09 12:01:36 +05:30
import tooltip from '../../vue_shared/directives/tooltip';
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: 'NoteableDiscussion',
2018-05-09 12:01:36 +05:30
components: {
noteableNote,
diffWithNote,
userAvatarLink,
noteHeader,
noteSignedOutWidget,
noteEditedText,
noteForm,
placeholderNote,
placeholderSystemNote,
2018-11-08 19:23:39 +05:30
systemNote,
2018-05-09 12:01:36 +05:30
},
directives: {
tooltip,
},
2018-11-18 11:00:15 +05:30
mixins: [autosave, noteable, resolvable, discussionNavigation],
2018-05-09 12:01:36 +05:30
props: {
2018-11-08 19:23:39 +05:30
discussion: {
2018-05-09 12:01:36 +05:30
type: Object,
required: true,
},
2018-11-08 19:23:39 +05:30
renderHeader: {
type: Boolean,
required: false,
default: true,
},
renderDiffFile: {
type: Boolean,
required: false,
default: true,
},
alwaysExpanded: {
type: Boolean,
required: false,
default: false,
},
2018-11-18 11:00:15 +05:30
discussionsByDiffOrder: {
type: Boolean,
required: false,
default: false,
},
2018-05-09 12:01:36 +05:30
},
data() {
return {
isReplying: false,
isResolving: false,
resolveAsThread: true,
};
},
computed: {
...mapGetters([
'getNoteableData',
'discussionCount',
'resolvedDiscussionCount',
2018-11-08 19:23:39 +05:30
'allDiscussions',
2018-11-18 11:00:15 +05:30
'unresolvedDiscussionsIdsByDiff',
'unresolvedDiscussionsIdsByDate',
2018-05-09 12:01:36 +05:30
'unresolvedDiscussions',
2018-11-18 11:00:15 +05:30
'unresolvedDiscussionsIdsOrdered',
'nextUnresolvedDiscussionId',
'isLastUnresolvedDiscussion',
2018-05-09 12:01:36 +05:30
]),
2018-11-08 19:23:39 +05:30
transformedDiscussion() {
2018-03-17 18:26:18 +05:30
return {
2018-11-08 19:23:39 +05:30
...this.discussion.notes[0],
truncatedDiffLines: this.discussion.truncated_diff_lines || [],
truncatedDiffLinesPath: this.discussion.truncated_diff_lines_path,
diffFile: this.discussion.diff_file,
diffDiscussion: this.discussion.diff_discussion,
imageDiffHtml: this.discussion.image_diff_html,
active: this.discussion.active,
discussionPath: this.discussion.discussion_path,
resolved: this.discussion.resolved,
resolvedBy: this.discussion.resolved_by,
resolvedByPush: this.discussion.resolved_by_push,
resolvedAt: this.discussion.resolved_at,
2018-03-17 18:26:18 +05:30
};
},
2018-05-09 12:01:36 +05:30
author() {
2018-11-08 19:23:39 +05:30
return this.transformedDiscussion.author;
2018-05-09 12:01:36 +05:30
},
canReply() {
return this.getNoteableData.current_user.can_create_note;
},
newNotePath() {
return this.getNoteableData.create_note_path;
},
lastUpdatedBy() {
2018-11-08 19:23:39 +05:30
const { notes } = this.discussion;
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
if (notes.length > 1) {
return notes[notes.length - 1].author;
}
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
return null;
},
lastUpdatedAt() {
2018-11-08 19:23:39 +05:30
const { notes } = this.discussion;
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
if (notes.length > 1) {
return notes[notes.length - 1].created_at;
}
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
return null;
},
2018-11-08 19:23:39 +05:30
resolvedText() {
return this.transformedDiscussion.resolvedByPush ? 'Automatically resolved' : 'Resolved';
},
hasMultipleUnresolvedDiscussions() {
return this.unresolvedDiscussions.length > 1;
},
2018-11-18 11:00:15 +05:30
showJumpToNextDiscussion() {
return this.hasMultipleUnresolvedDiscussions &&
!this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder);
},
2018-11-08 19:23:39 +05:30
shouldRenderDiffs() {
const { diffDiscussion, diffFile } = this.transformedDiscussion;
return diffDiscussion && diffFile && this.renderDiffFile;
2018-05-09 12:01:36 +05:30
},
wrapperComponent() {
2018-11-08 19:23:39 +05:30
return this.shouldRenderDiffs ? diffWithNote : 'div';
},
wrapperComponentProps() {
if (this.shouldRenderDiffs) {
return { discussion: convertObjectPropsToCamelCase(this.discussion) };
}
return {};
2018-05-09 12:01:36 +05:30
},
wrapperClass() {
2018-11-08 19:23:39 +05:30
return this.isDiffDiscussion ? '' : 'card discussion-wrapper';
2018-05-09 12:01:36 +05:30
},
},
2018-11-18 11:00:15 +05:30
watch: {
isReplying() {
if (this.isReplying) {
this.$nextTick(() => {
// Pass an extra key to separate reply and note edit forms
this.initAutoSave(this.transformedDiscussion, ['Reply']);
});
2018-05-09 12:01:36 +05:30
} else {
2018-11-18 11:00:15 +05:30
this.disposeAutoSave();
2018-03-17 18:26:18 +05:30
}
2018-11-18 11:00:15 +05:30
},
2018-05-09 12:01:36 +05:30
},
created() {
this.resolveDiscussionsSvg = resolveDiscussionsSvg;
this.nextDiscussionsSvg = nextDiscussionsSvg;
},
methods: {
...mapActions([
'saveNote',
'toggleDiscussion',
'removePlaceholderNotes',
'toggleResolveNote',
2018-11-08 19:23:39 +05:30
'expandDiscussion',
2018-05-09 12:01:36 +05:30
]),
2018-11-08 19:23:39 +05:30
truncateSha,
2018-05-09 12:01:36 +05:30
componentName(note) {
if (note.isPlaceholderNote) {
if (note.placeholderType === SYSTEM_NOTE) {
return placeholderSystemNote;
2018-03-17 18:26:18 +05:30
}
2018-05-09 12:01:36 +05:30
return placeholderNote;
2018-03-17 18:26:18 +05:30
}
2018-05-09 12:01:36 +05:30
2018-11-08 19:23:39 +05:30
if (note.system) {
return systemNote;
}
2018-05-09 12:01:36 +05:30
return noteableNote;
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
componentData(note) {
2018-11-08 19:23:39 +05:30
return note.isPlaceholderNote ? this.discussion.notes[0] : note;
2018-05-09 12:01:36 +05:30
},
toggleDiscussionHandler() {
2018-11-08 19:23:39 +05:30
this.toggleDiscussion({ discussionId: this.discussion.id });
2018-05-09 12:01:36 +05:30
},
showReplyForm() {
this.isReplying = true;
},
2018-11-18 11:00:15 +05:30
cancelReplyForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
2018-05-09 12:01:36 +05:30
// eslint-disable-next-line no-alert
2018-11-18 11:00:15 +05:30
if (!window.confirm(msg)) {
2018-05-09 12:01:36 +05:30
return;
2018-03-17 18:26:18 +05:30
}
2018-05-09 12:01:36 +05:30
}
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
this.isReplying = false;
2018-11-18 11:00:15 +05:30
this.resetAutoSave();
2018-05-09 12:01:36 +05:30
},
saveReply(noteText, form, callback) {
2018-11-08 19:23:39 +05:30
const postData = {
in_reply_to_discussion_id: this.discussion.reply_id,
target_type: this.getNoteableData.targetType,
note: { note: noteText },
};
if (this.discussion.for_commit) {
postData.note_project_id = this.discussion.project_id;
}
2018-05-09 12:01:36 +05:30
const replyData = {
endpoint: this.newNotePath,
flashContainer: this.$el,
2018-11-08 19:23:39 +05:30
data: postData,
2018-05-09 12:01:36 +05:30
};
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
this.isReplying = false;
2018-05-09 12:01:36 +05:30
this.saveNote(replyData)
.then(() => {
this.resetAutoSave();
callback();
})
.catch(err => {
this.removePlaceholderNotes();
this.isReplying = true;
this.$nextTick(() => {
const msg = `Your comment could not be submitted!
2018-03-17 18:26:18 +05:30
Please check your network connection and try again.`;
2018-05-09 12:01:36 +05:30
Flash(msg, 'alert', this.$el);
this.$refs.noteForm.note = noteText;
callback(err);
2018-03-17 18:26:18 +05:30
});
2018-05-09 12:01:36 +05:30
});
},
2018-11-08 19:23:39 +05:30
jumpToNextDiscussion() {
2018-11-18 11:00:15 +05:30
const nextId =
this.nextUnresolvedDiscussionId(this.discussion.id, this.discussionsByDiffOrder);
2018-03-27 19:54:05 +05:30
2018-11-18 11:00:15 +05:30
this.jumpToDiscussion(nextId);
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
},
};
2018-03-17 18:26:18 +05:30
</script>
<template>
2018-11-08 19:23:39 +05:30
<li class="note note-discussion timeline-entry">
2018-03-17 18:26:18 +05:30
<div class="timeline-entry-inner">
<div class="timeline-icon">
<user-avatar-link
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
/>
</div>
<div class="timeline-content">
2018-11-08 19:23:39 +05:30
<div
:data-discussion-id="transformedDiscussion.discussion_id"
class="discussion js-discussion-container"
>
<div
v-if="renderHeader"
class="discussion-header"
>
2018-03-17 18:26:18 +05:30
<note-header
:author="author"
2018-11-08 19:23:39 +05:30
:created-at="transformedDiscussion.created_at"
:note-id="transformedDiscussion.id"
2018-03-17 18:26:18 +05:30
:include-toggle="true"
2018-11-08 19:23:39 +05:30
:expanded="discussion.expanded"
2018-03-17 18:26:18 +05:30
@toggleHandler="toggleDiscussionHandler"
2018-11-08 19:23:39 +05:30
>
<template v-if="transformedDiscussion.diffDiscussion">
started a discussion on
<a :href="transformedDiscussion.discussionPath">
<template v-if="transformedDiscussion.active">
the diff
</template>
<template v-else>
an old version of the diff
</template>
</a>
</template>
<template v-else-if="discussion.for_commit">
started a discussion on commit
<a :href="discussion.discussion_path">
{{ truncateSha(discussion.commit_id) }}
</a>
</template>
<template v-else>
started a discussion
</template>
</note-header>
<note-edited-text
v-if="transformedDiscussion.resolved"
:edited-at="transformedDiscussion.resolvedAt"
:edited-by="transformedDiscussion.resolvedBy"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline"
2018-03-17 18:26:18 +05:30
/>
<note-edited-text
2018-11-08 19:23:39 +05:30
v-else-if="lastUpdatedAt"
2018-03-17 18:26:18 +05:30
:edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy"
action-text="Last updated"
class-name="discussion-headline-light js-discussion-headline"
/>
</div>
2018-03-27 19:54:05 +05:30
<div
2018-11-08 19:23:39 +05:30
v-if="discussion.expanded || alwaysExpanded"
2018-03-27 19:54:05 +05:30
class="discussion-body">
<component
:is="wrapperComponent"
2018-11-08 19:23:39 +05:30
v-bind="wrapperComponentProps"
2018-03-27 19:54:05 +05:30
:class="wrapperClass"
>
<div class="discussion-notes">
<ul class="notes">
<component
2018-11-08 19:23:39 +05:30
v-for="note in discussion.notes"
2018-03-27 19:54:05 +05:30
:is="componentName(note)"
:note="componentData(note)"
:key="note.id"
/>
</ul>
<div
:class="{ 'is-replying': isReplying }"
2018-11-08 19:23:39 +05:30
class="discussion-reply-holder"
>
2018-03-27 19:54:05 +05:30
<template v-if="!isReplying && canReply">
<div
2018-11-08 19:23:39 +05:30
class="btn-group d-flex discussion-with-resolve-btn"
2018-03-27 19:54:05 +05:30
role="group">
<div
2018-11-08 19:23:39 +05:30
class="btn-group w-100"
2018-03-27 19:54:05 +05:30
role="group">
<button
type="button"
2018-11-08 19:23:39 +05:30
class="js-vue-discussion-reply btn btn-text-field mr-2"
title="Add a reply"
@click="showReplyForm">Reply...</button>
2018-03-27 19:54:05 +05:30
</div>
<div
2018-11-08 19:23:39 +05:30
v-if="discussion.resolvable"
2018-03-27 19:54:05 +05:30
class="btn-group"
role="group">
<button
type="button"
2018-11-08 19:23:39 +05:30
class="btn btn-default mr-2"
@click="resolveHandler()"
2018-03-27 19:54:05 +05:30
>
<i
v-if="isResolving"
aria-hidden="true"
class="fa fa-spinner fa-spin"
></i>
{{ resolveButtonTitle }}
</button>
</div>
<div
2018-11-08 19:23:39 +05:30
v-if="discussion.resolvable"
2018-03-27 19:54:05 +05:30
class="btn-group discussion-actions"
2018-05-01 15:08:00 +05:30
role="group"
>
2018-03-27 19:54:05 +05:30
<div
2018-05-01 15:08:00 +05:30
v-if="!discussionResolved"
2018-03-27 19:54:05 +05:30
class="btn-group"
role="group">
<a
v-tooltip
2018-11-08 19:23:39 +05:30
:href="discussion.resolve_with_issue_path"
:title="s__('MergeRequests|Resolve this discussion in a new issue')"
2018-03-27 19:54:05 +05:30
class="new-issue-for-discussion btn
btn-default discussion-create-issue-btn"
data-container="body"
>
<span v-html="resolveDiscussionsSvg"></span>
</a>
</div>
<div
2018-11-18 11:00:15 +05:30
v-if="showJumpToNextDiscussion"
2018-03-27 19:54:05 +05:30
class="btn-group"
role="group">
<button
v-tooltip
class="btn btn-default discussion-next-btn"
title="Jump to next unresolved discussion"
data-container="body"
2018-11-08 19:23:39 +05:30
@click="jumpToNextDiscussion"
2018-03-27 19:54:05 +05:30
>
<span v-html="nextDiscussionsSvg"></span>
</button>
</div>
</div>
</div>
</template>
<note-form
v-if="isReplying"
2018-11-08 19:23:39 +05:30
ref="noteForm"
:discussion="discussion"
2018-03-27 19:54:05 +05:30
:is-editing="false"
2018-11-08 19:23:39 +05:30
save-button-title="Comment"
2018-03-27 19:54:05 +05:30
@handleFormUpdate="saveReply"
2018-11-18 11:00:15 +05:30
@cancelForm="cancelReplyForm"
/>
2018-03-27 19:54:05 +05:30
<note-signed-out-widget v-if="!canReply" />
</div>
2018-03-17 18:26:18 +05:30
</div>
2018-03-27 19:54:05 +05:30
</component>
2018-03-17 18:26:18 +05:30
</div>
</div>
</div>
</div>
</li>
</template>