diff options
Diffstat (limited to 'app/assets/javascripts/notes')
12 files changed, 142 insertions, 86 deletions
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index 376d4114efd..d8947e8ca50 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -5,6 +5,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue'; import { GlSkeletonLoading } from '@gitlab/ui'; import { getDiffMode } from '~/diffs/store/utils'; +import { diffViewerModes } from '~/ide/constants'; export default { components: { @@ -31,6 +32,12 @@ export default { diffMode() { return getDiffMode(this.discussion.diff_file); }, + diffViewerMode() { + return this.discussion.diff_file.viewer.name; + }, + isTextFile() { + return this.diffViewerMode === diffViewerModes.text; + }, hasTruncatedDiffLines() { return ( this.discussion.truncated_diff_lines && this.discussion.truncated_diff_lines.length !== 0 @@ -58,18 +65,14 @@ export default { </script> <template> - <div :class="{ 'text-file': discussion.diff_file.text }" class="diff-file file-holder"> + <div :class="{ 'text-file': isTextFile }" class="diff-file file-holder"> <diff-file-header :discussion-path="discussion.discussion_path" :diff-file="discussion.diff_file" :can-current-user-fork="false" - :expanded="!discussion.diff_file.collapsed" + :expanded="!discussion.diff_file.viewer.collapsed" /> - <div - v-if="discussion.diff_file.text" - :class="$options.userColorSchemeClass" - class="diff-content code" - > + <div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code"> <table> <template v-if="hasTruncatedDiffLines"> <tr @@ -109,6 +112,7 @@ export default { <div v-else> <diff-viewer :diff-mode="diffMode" + :diff-viewer-mode="diffViewerMode" :new-path="discussion.diff_file.new_path" :new-sha="discussion.diff_file.diff_refs.head_sha" :old-path="discussion.diff_file.old_path" diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index 91b9e5de374..de1ea0f58d6 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -23,11 +23,6 @@ export default { type: [String, Number], required: true, }, - discussionId: { - type: String, - required: false, - default: '', - }, noteUrl: { type: String, required: false, @@ -126,6 +121,11 @@ export default { onResolve() { this.$emit('handleResolve'); }, + closeTooltip() { + this.$nextTick(() => { + this.$root.$emit('bv::hide::tooltip'); + }); + }, }, }; </script> @@ -171,7 +171,7 @@ export default { v-if="showReplyButton" ref="replyButton" class="js-reply-button" - :note-id="discussionId" + @startReplying="$emit('startReplying')" /> <div v-if="canEdit" class="note-actions-item"> <button @@ -202,6 +202,7 @@ export default { title="More actions" class="note-action-button more-actions-toggle btn btn-transparent" data-toggle="dropdown" + @click="closeTooltip" > <icon css-classes="icon" name="ellipsis_v" /> </button> diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue index b2f9d7f128a..f50cab81efe 100644 --- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue +++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue @@ -1,5 +1,4 @@ <script> -import { mapActions } from 'vuex'; import { GlTooltipDirective, GlButton } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; @@ -12,15 +11,6 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - props: { - noteId: { - type: String, - required: true, - }, - }, - methods: { - ...mapActions(['convertToDiscussion']), - }, }; </script> @@ -32,7 +22,7 @@ export default { class="note-action-button" variant="transparent" :title="__('Reply to comment')" - @click="convertToDiscussion(noteId)" + @click="$emit('startReplying')" > <icon name="comment" css-classes="link-highlight" /> </gl-button> diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index ff303d0f55a..fb1d98355b3 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -95,6 +95,7 @@ export default { <div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body"> <suggestions v-if="hasSuggestion && !isEditing" + class="note-text md" :suggestions="note.suggestions" :note-html="note.note_html" :line-type="lineType" diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index b7e9f7c2028..2d6fd8b116f 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -26,6 +26,7 @@ import resolvable from '../mixins/resolvable'; import discussionNavigation from '../mixins/discussion_navigation'; import ReplyPlaceholder from './discussion_reply_placeholder.vue'; import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue'; +import eventHub from '../event_hub'; export default { name: 'NoteableDiscussion', @@ -93,6 +94,7 @@ export default { }, computed: { ...mapGetters([ + 'convertedDisscussionIds', 'getNoteableData', 'nextUnresolvedDiscussionId', 'unresolvedDiscussionsCount', @@ -245,6 +247,12 @@ export default { } }, }, + created() { + eventHub.$on('startReplying', this.onStartReplying); + }, + beforeDestroy() { + eventHub.$off('startReplying', this.onStartReplying); + }, methods: { ...mapActions([ 'saveNote', @@ -252,6 +260,7 @@ export default { 'removePlaceholderNotes', 'toggleResolveNote', 'expandDiscussion', + 'removeConvertedDiscussion', ]), truncateSha, componentName(note) { @@ -291,6 +300,10 @@ export default { } } + if (this.convertedDisscussionIds.includes(this.discussion.id)) { + this.removeConvertedDiscussion(this.discussion.id); + } + this.isReplying = false; this.resetAutoSave(); }, @@ -301,6 +314,10 @@ export default { note: { note: noteText }, }; + if (this.convertedDisscussionIds.includes(this.discussion.id)) { + postData.return_discussion = true; + } + if (this.discussion.for_commit) { postData.note_project_id = this.discussion.project_id; } @@ -340,6 +357,11 @@ Please check your network connection and try again.`; deleteNoteHandler(note) { this.$emit('noteDeleted', this.discussion, note); }, + onStartReplying(discussionId) { + if (this.discussion.id === discussionId) { + this.showReplyForm(); + } + }, }, }; </script> @@ -358,30 +380,32 @@ Please check your network connection and try again.`; :img-size="40" /> </div> - <note-header - :author="author" - :created-at="initialDiscussion.created_at" - :note-id="initialDiscussion.id" - :include-toggle="true" - :expanded="discussion.expanded" - @toggleHandler="toggleDiscussionHandler" - > - <span v-html="actionText"></span> - </note-header> - <note-edited-text - v-if="discussion.resolved" - :edited-at="discussion.resolved_at" - :edited-by="discussion.resolved_by" - :action-text="resolvedText" - class-name="discussion-headline-light js-discussion-headline" - /> - <note-edited-text - v-else-if="lastUpdatedAt" - :edited-at="lastUpdatedAt" - :edited-by="lastUpdatedBy" - action-text="Last updated" - class-name="discussion-headline-light js-discussion-headline" - /> + <div class="timeline-content"> + <note-header + :author="author" + :created-at="initialDiscussion.created_at" + :note-id="initialDiscussion.id" + :include-toggle="true" + :expanded="discussion.expanded" + @toggleHandler="toggleDiscussionHandler" + > + <span v-html="actionText"></span> + </note-header> + <note-edited-text + v-if="discussion.resolved" + :edited-at="discussion.resolved_at" + :edited-by="discussion.resolved_by" + :action-text="resolvedText" + class-name="discussion-headline-light js-discussion-headline" + /> + <note-edited-text + v-else-if="lastUpdatedAt" + :edited-at="lastUpdatedAt" + :edited-by="lastUpdatedBy" + action-text="Last updated" + class-name="discussion-headline-light js-discussion-headline" + /> + </div> </div> <div v-if="shouldShowDiscussions" class="discussion-body"> <component @@ -400,6 +424,7 @@ Please check your network connection and try again.`; :help-page-path="helpPagePath" :show-reply-button="canReply" @handleDeleteNote="deleteNoteHandler" + @startReplying="showReplyForm" > <note-edited-text v-if="discussion.resolved" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 56108a58010..04e74a43acc 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -29,11 +29,6 @@ export default { type: Object, required: true, }, - discussion: { - type: Object, - required: false, - default: null, - }, line: { type: Object, required: false, @@ -49,6 +44,11 @@ export default { required: false, default: () => null, }, + showReplyButton: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -91,13 +91,6 @@ export default { } return ''; }, - showReplyButton() { - if (!this.discussion || !this.getNoteableData.current_user.can_create_note) { - return false; - } - - return this.discussion.individual_note && !this.commentsDisabled; - }, actionText() { if (!this.commit) { return ''; @@ -260,10 +253,10 @@ export default { :is-resolved="note.resolved" :is-resolving="isResolving" :resolved-by="note.resolved_by" - :discussion-id="discussionId" @handleEdit="editHandler" @handleDelete="deleteHandler" @handleResolve="resolveHandler" + @startReplying="$emit('startReplying')" /> </div> <div class="timeline-discussion-body"> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 6d72b72e628..a63571edcea 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -60,9 +60,11 @@ export default { ...mapGetters([ 'isNotesFetched', 'discussions', + 'convertedDisscussionIds', 'getNotesDataByProp', 'isLoading', 'commentsDisabled', + 'getNoteableData', ]), noteableType() { return this.noteableData.noteableType; @@ -78,6 +80,9 @@ export default { return this.discussions; }, + canReply() { + return this.getNoteableData.current_user.can_create_note && !this.commentsDisabled; + }, }, watch: { shouldShow() { @@ -128,6 +133,7 @@ export default { 'setNotesFetchedState', 'expandDiscussion', 'startTaskList', + 'convertToDiscussion', ]), fetchNotes() { if (this.isFetching) return null; @@ -175,6 +181,11 @@ export default { } } }, + startReplying(discussionId) { + return this.convertToDiscussion(discussionId) + .then(() => this.$nextTick()) + .then(() => eventHub.$emit('startReplying', discussionId)); + }, }, systemNote: constants.SYSTEM_NOTE, }; @@ -193,7 +204,9 @@ export default { /> <placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" /> </template> - <template v-else-if="discussion.individual_note"> + <template + v-else-if="discussion.individual_note && !convertedDisscussionIds.includes(discussion.id)" + > <system-note v-if="discussion.notes[0].system" :key="discussion.id" @@ -203,7 +216,8 @@ export default { v-else :key="discussion.id" :note="discussion.notes[0]" - :discussion="discussion" + :show-reply-button="canReply" + @startReplying="startReplying(discussion.id)" /> </template> <noteable-discussion diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index ff65f14d529..1a0dba69a7c 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -83,12 +83,44 @@ export const updateNote = ({ commit, dispatch }, { endpoint, note }) => dispatch('startTaskList'); }); -export const replyToDiscussion = ({ commit }, { endpoint, data }) => +export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => { + const { notesById } = getters; + + notes.forEach(note => { + if (notesById[note.id]) { + commit(types.UPDATE_NOTE, note); + } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { + const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id); + + if (discussion) { + commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); + } else if (note.type === constants.DIFF_NOTE) { + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); + } else { + commit(types.ADD_NEW_NOTE, note); + } + } else { + commit(types.ADD_NEW_NOTE, note); + } + }); +}; + +export const replyToDiscussion = ({ commit, state, getters, dispatch }, { endpoint, data }) => service .replyToDiscussion(endpoint, data) .then(res => res.json()) .then(res => { - commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res); + if (res.discussion) { + commit(types.UPDATE_DISCUSSION, res.discussion); + + updateOrCreateNotes({ commit, state, getters, dispatch }, res.discussion.notes); + + dispatch('updateMergeRequestWidget'); + dispatch('startTaskList'); + dispatch('updateResolvableDiscussonsCounts'); + } else { + commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res); + } return res; }); @@ -262,25 +294,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { if (resp.notes && resp.notes.length) { - const { notesById } = getters; - - resp.notes.forEach(note => { - if (notesById[note.id]) { - commit(types.UPDATE_NOTE, note); - } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { - const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id); - - if (discussion) { - commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); - } else if (note.type === constants.DIFF_NOTE) { - dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); - } else { - commit(types.ADD_NEW_NOTE, note); - } - } else { - commit(types.ADD_NEW_NOTE, note); - } - }); + updateOrCreateNotes({ commit, state, getters, dispatch }, resp.notes); dispatch('startTaskList'); } @@ -429,5 +443,8 @@ export const submitSuggestion = ( export const convertToDiscussion = ({ commit }, noteId) => commit(types.CONVERT_TO_DISCUSSION, noteId); +export const removeConvertedDiscussion = ({ commit }, noteId) => + commit(types.REMOVE_CONVERTED_DISCUSSION, noteId); + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 0ffc0cb2593..5026c13dab5 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -4,6 +4,8 @@ import { collapseSystemNotes } from './collapse_utils'; export const discussions = state => collapseSystemNotes(state.discussions); +export const convertedDisscussionIds = state => state.convertedDisscussionIds; + export const targetNoteHash = state => state.targetNoteHash; export const getNotesData = state => state.notesData; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 887e6d22b06..6168aeae35d 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -5,6 +5,7 @@ import mutations from '../mutations'; export default () => ({ state: { discussions: [], + convertedDisscussionIds: [], targetNoteHash: null, lastFetchedAt: null, diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 2bffedad336..796370920bb 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -18,6 +18,7 @@ export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; export const DISABLE_COMMENTS = 'DISABLE_COMMENTS'; export const APPLY_SUGGESTION = 'APPLY_SUGGESTION'; export const CONVERT_TO_DISCUSSION = 'CONVERT_TO_DISCUSSION'; +export const REMOVE_CONVERTED_DISCUSSION = 'REMOVE_CONVERTED_DISCUSSION'; // DISCUSSION export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index d167f8ef421..ae6f8b7790a 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -266,7 +266,14 @@ export default { }, [types.CONVERT_TO_DISCUSSION](state, discussionId) { - const discussion = utils.findNoteObjectById(state.discussions, discussionId); - Object.assign(discussion, { individual_note: false }); + const convertedDisscussionIds = [...state.convertedDisscussionIds, discussionId]; + Object.assign(state, { convertedDisscussionIds }); + }, + + [types.REMOVE_CONVERTED_DISCUSSION](state, discussionId) { + const convertedDisscussionIds = [...state.convertedDisscussionIds]; + + convertedDisscussionIds.splice(convertedDisscussionIds.indexOf(discussionId), 1); + Object.assign(state, { convertedDisscussionIds }); }, }; |