diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /app/assets/javascripts/notes | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/notes')
31 files changed, 359 insertions, 286 deletions
diff --git a/app/assets/javascripts/notes/components/comment_field_layout.vue b/app/assets/javascripts/notes/components/comment_field_layout.vue new file mode 100644 index 00000000000..aaf64702ffd --- /dev/null +++ b/app/assets/javascripts/notes/components/comment_field_layout.vue @@ -0,0 +1,69 @@ +<script> +import EmailParticipantsWarning from './email_participants_warning.vue'; +import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue'; + +const DEFAULT_NOTEABLE_TYPE = 'Issue'; + +export default { + components: { + EmailParticipantsWarning, + NoteableWarning, + }, + props: { + noteableData: { + type: Object, + required: true, + }, + noteableType: { + type: String, + required: false, + default: DEFAULT_NOTEABLE_TYPE, + }, + withAlertContainer: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + isLocked() { + return Boolean(this.noteableData.discussion_locked); + }, + isConfidential() { + return Boolean(this.noteableData.confidential); + }, + hasWarning() { + return this.isConfidential || this.isLocked; + }, + emailParticipants() { + return this.noteableData.issue_email_participants?.map(({ email }) => email) || []; + }, + }, +}; +</script> +<template> + <div + class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-base gl-border-gray-100" + > + <div + v-if="withAlertContainer" + class="error-alert" + data-testid="comment-field-alert-container" + ></div> + <noteable-warning + v-if="hasWarning" + class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100 gl-rounded-base gl-rounded-bottom-left-none gl-rounded-bottom-right-none" + :is-locked="isLocked" + :is-confidential="isConfidential" + :noteable-type="noteableType" + :locked-noteable-docs-path="noteableData.locked_discussion_docs_path" + :confidential-noteable-docs-path="noteableData.confidential_issues_docs_path" + /> + <slot></slot> + <email-participants-warning + v-if="emailParticipants.length" + class="gl-border-t-1 gl-border-t-solid gl-border-t-gray-100 gl-rounded-base gl-rounded-top-left-none! gl-rounded-top-right-none!" + :emails="emailParticipants" + /> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 0363173f912..111af977ec5 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -17,17 +17,17 @@ import { import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import * as constants from '../constants'; import eventHub from '../event_hub'; -import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue'; import markdownField from '~/vue_shared/components/markdown/field.vue'; import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import noteSignedOutWidget from './note_signed_out_widget.vue'; import discussionLockedWidget from './discussion_locked_widget.vue'; import issuableStateMixin from '../mixins/issuable_state'; +import CommentFieldLayout from './comment_field_layout.vue'; export default { name: 'CommentForm', components: { - NoteableWarning, noteSignedOutWidget, discussionLockedWidget, markdownField, @@ -35,8 +35,9 @@ export default { GlButton, TimelineEntryItem, GlIcon, + CommentFieldLayout, }, - mixins: [issuableStateMixin], + mixins: [glFeatureFlagsMixin(), issuableStateMixin], props: { noteableType: { type: String, @@ -286,6 +287,9 @@ export default { Autosize.update(this.$refs.textarea); }); }, + hasEmailParticipants() { + return this.getNoteableData.issue_email_participants?.length; + }, }, }; </script> @@ -308,46 +312,41 @@ export default { </div> <div class="timeline-content timeline-content-form"> <form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form"> - <div class="error-alert"></div> - - <noteable-warning - v-if="hasWarning(getNoteableData)" - :is-locked="isLocked(getNoteableData)" - :is-confidential="isConfidential(getNoteableData)" + <comment-field-layout + :with-alert-container="true" + :noteable-data="getNoteableData" :noteable-type="noteableType" - :locked-noteable-docs-path="lockedIssueDocsPath" - :confidential-noteable-docs-path="confidentialIssueDocsPath" - /> - - <markdown-field - ref="markdownField" - :is-submitting="isSubmitting" - :markdown-preview-path="markdownPreviewPath" - :markdown-docs-path="markdownDocsPath" - :quick-actions-docs-path="quickActionsDocsPath" - :add-spacing-classes="false" - :textarea-value="note" > - <textarea - id="note-body" - ref="textarea" - slot="textarea" - v-model="note" - dir="auto" - :disabled="isSubmitting" - name="note[note]" - class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area" - data-qa-selector="comment_field" - data-testid="comment-field" - data-supports-quick-actions="true" - :aria-label="__('Description')" - :placeholder="__('Write a comment or drag your files here…')" - @keydown.up="editCurrentUserLastNote()" - @keydown.meta.enter="handleSave()" - @keydown.ctrl.enter="handleSave()" - ></textarea> - </markdown-field> - + <markdown-field + ref="markdownField" + :is-submitting="isSubmitting" + :markdown-preview-path="markdownPreviewPath" + :markdown-docs-path="markdownDocsPath" + :quick-actions-docs-path="quickActionsDocsPath" + :add-spacing-classes="false" + :textarea-value="note" + > + <template #textarea> + <textarea + id="note-body" + ref="textarea" + v-model="note" + dir="auto" + :disabled="isSubmitting" + name="note[note]" + class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area" + data-qa-selector="comment_field" + data-testid="comment-field" + :data-supports-quick-actions="!glFeatures.tributeAutocomplete" + :aria-label="__('Description')" + :placeholder="__('Write a comment or drag your files here…')" + @keydown.up="editCurrentUserLastNote()" + @keydown.meta.enter="handleSave()" + @keydown.ctrl.enter="handleSave()" + ></textarea> + </template> + </markdown-field> + </comment-field-layout> <div class="note-form-actions"> <div class="btn-group gl-mr-3 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index 1580c94658a..b7355d4d927 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -31,7 +31,7 @@ export default { }, computed: { ...mapState({ - projectPath: state => state.diffs.projectPath, + projectPath: (state) => state.diffs.projectPath, }), diffMode() { return getDiffMode(this.discussion.diff_file); diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue index 0272790a75d..da4134ab2c4 100644 --- a/app/assets/javascripts/notes/components/discussion_actions.vue +++ b/app/assets/javascripts/notes/components/discussion_actions.vue @@ -2,7 +2,6 @@ import ReplyPlaceholder from './discussion_reply_placeholder.vue'; import ResolveDiscussionButton from './discussion_resolve_button.vue'; import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue'; -import JumpToNextDiscussionButton from './discussion_jump_to_next_button.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { @@ -11,7 +10,6 @@ export default { ReplyPlaceholder, ResolveDiscussionButton, ResolveWithIssueButton, - JumpToNextDiscussionButton, }, mixins: [glFeatureFlagsMixin()], props: { @@ -38,14 +36,11 @@ export default { }, }, computed: { - hideJumpToNextUnresolvedInThreads() { - return this.glFeatures.hideJumpToNextUnresolvedInThreads; - }, resolvableNotes() { - return this.discussion.notes.filter(x => x.resolvable); + return this.discussion.notes.filter((x) => x.resolvable); }, userCanResolveDiscussion() { - return this.resolvableNotes.every(note => note.current_user?.can_resolve_discussion); + return this.resolvableNotes.every((note) => note.current_user?.can_resolve_discussion); }, }, }; @@ -74,15 +69,5 @@ export default { :url="resolveWithIssuePath" /> </div> - <div - v-if=" - !hideJumpToNextUnresolvedInThreads && - discussion.resolvable && - shouldShowJumpToNextDiscussion - " - class="btn-group discussion-actions ml-sm-2" - > - <jump-to-next-discussion-button :from-discussion-id="discussion.id" /> - </div> </div> </template> diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index 2427a3f98ad..0a72627834d 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -32,10 +32,10 @@ export default { return this.getNoteableData.create_issue_to_resolve_discussions_path; }, toggeableDiscussions() { - return this.discussions.filter(discussion => !discussion.individual_note); + return this.discussions.filter((discussion) => !discussion.individual_note); }, allExpanded() { - return this.toggeableDiscussions.every(discussion => discussion.expanded); + return this.toggeableDiscussions.every((discussion) => discussion.expanded); }, lineResolveClass() { return this.allResolved ? 'line-resolve-btn is-active' : 'line-resolve-text'; @@ -48,7 +48,7 @@ export default { ...mapActions(['setExpandDiscussions']), handleExpandDiscussions() { this.setExpandDiscussions({ - discussionIds: this.toggeableDiscussions.map(discussion => discussion.id), + discussionIds: this.toggeableDiscussions.map((discussion) => discussion.id), expanded: !this.allExpanded, }); }, diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index 08c22f0b4c6..aa61aa9b3cb 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -42,7 +42,7 @@ export default { ...mapGetters(['getNotesDataByProp', 'timelineEnabled']), currentFilter() { if (!this.currentValue) return this.filters[0]; - return this.filters.find(filter => filter.value === this.currentValue); + return this.filters.find((filter) => filter.value === this.currentValue); }, }, created() { diff --git a/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue b/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue deleted file mode 100644 index f94d0060b41..00000000000 --- a/app/assets/javascripts/notes/components/discussion_jump_to_next_button.vue +++ /dev/null @@ -1,38 +0,0 @@ -<script> -import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; -import discussionNavigation from '../mixins/discussion_navigation'; - -export default { - name: 'JumpToNextDiscussionButton', - components: { - GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [discussionNavigation], - props: { - fromDiscussionId: { - type: String, - required: true, - }, - }, -}; -</script> - -<template> - <div class="btn-group" role="group"> - <button - ref="button" - v-gl-tooltip - class="btn btn-default discussion-next-btn" - :title="s__('MergeRequests|Jump to next unresolved thread')" - data-track-event="click_button" - data-track-label="mr_next_unresolved_thread" - data-track-property="click_next_unresolved_thread" - @click="jumpToNextRelativeDiscussion(fromDiscussionId)" - > - <gl-icon name="comment-next" /> - </button> - </div> -</template> diff --git a/app/assets/javascripts/notes/components/email_participants_warning.vue b/app/assets/javascripts/notes/components/email_participants_warning.vue new file mode 100644 index 00000000000..bb1ff58120a --- /dev/null +++ b/app/assets/javascripts/notes/components/email_participants_warning.vue @@ -0,0 +1,70 @@ +<script> +import { GlSprintf } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; +import { toNounSeriesText } from '~/lib/utils/grammar'; + +export default { + components: { + GlSprintf, + }, + props: { + emails: { + type: Array, + required: true, + }, + numberOfLessParticipants: { + type: Number, + required: false, + default: 3, + }, + }, + data() { + return { + isShowingMoreParticipants: false, + }; + }, + computed: { + title() { + return this.moreParticipantsAvailable + ? toNounSeriesText(this.lessParticipants, { onlyCommas: true }) + : toNounSeriesText(this.emails); + }, + lessParticipants() { + return this.emails.slice(0, this.numberOfLessParticipants); + }, + moreLabel() { + return sprintf(s__('EmailParticipantsWarning|and %{moreCount} more'), { + moreCount: this.emails.length - this.numberOfLessParticipants, + }); + }, + moreParticipantsAvailable() { + return !this.isShowingMoreParticipants && this.emails.length > this.numberOfLessParticipants; + }, + message() { + return this.moreParticipantsAvailable + ? s__('EmailParticipantsWarning|%{emails}, %{andMore} will be notified of your comment.') + : s__('EmailParticipantsWarning|%{emails} will be notified of your comment.'); + }, + }, + methods: { + showMoreParticipants() { + this.isShowingMoreParticipants = true; + }, + }, +}; +</script> + +<template> + <div class="issuable-note-warning" data-testid="email-participants-warning"> + <gl-sprintf :message="message"> + <template #andMore> + <button type="button" class="btn-transparent btn-link" @click="showMoreParticipants"> + {{ moreLabel }} + </button> + </template> + <template #emails> + <span>{{ title }}</span> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/multiline_comment_form.vue b/app/assets/javascripts/notes/components/multiline_comment_form.vue index bb13eb87af7..9fbf2c9265c 100644 --- a/app/assets/javascripts/notes/components/multiline_comment_form.vue +++ b/app/assets/javascripts/notes/components/multiline_comment_form.vue @@ -1,5 +1,5 @@ <script> -import { mapActions } from 'vuex'; +import { mapActions, mapState } from 'vuex'; import { GlFormSelect, GlSprintf } from '@gitlab/ui'; import { getSymbol, getLineClasses } from './multiline_comment_utils'; @@ -27,12 +27,13 @@ export default { }; }, computed: { + ...mapState({ selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition }), lineNumber() { return this.commentLineOptions[this.commentLineOptions.length - 1].text; }, }, created() { - const line = this.lineRange?.start || this.line; + const line = this.selectedCommentPosition?.start || this.lineRange?.start || this.line; this.commentLineStart = { line_code: line.line_code, @@ -40,6 +41,8 @@ export default { old_line: line.old_line, new_line: line.new_line, }; + + if (this.selectedCommentPosition) return; this.highlightSelection(); }, destroyed() { diff --git a/app/assets/javascripts/notes/components/multiline_comment_utils.js b/app/assets/javascripts/notes/components/multiline_comment_utils.js index 2451400e980..4991695b97e 100644 --- a/app/assets/javascripts/notes/components/multiline_comment_utils.js +++ b/app/assets/javascripts/notes/components/multiline_comment_utils.js @@ -48,11 +48,11 @@ export function getLineClasses(line) { export function commentLineOptions(diffLines, startingLine, lineCode, side = 'left') { const preferredSide = side === 'left' ? 'old_line' : 'new_line'; const fallbackSide = preferredSide === 'new_line' ? 'old_line' : 'new_line'; - const notMatchType = l => l.type !== 'match'; + const notMatchType = (l) => l.type !== 'match'; const linesCopy = [...diffLines]; // don't mutate the argument const startingLineCode = startingLine.line_code; - const currentIndex = linesCopy.findIndex(line => line.line_code === lineCode); + const currentIndex = linesCopy.findIndex((line) => line.line_code === lineCode); // We're limiting adding comments to only lines above the current line // to make rendering simpler. Future interations will use a more @@ -66,10 +66,10 @@ export function commentLineOptions(diffLines, startingLine, lineCode, side = 'le // If the selected line is "hidden" in an unchanged line block // or "above" the current group of lines add it to the array so // that the drop down is not defaulted to empty - const selectedIndex = lines.findIndex(line => line.line_code === startingLineCode); + const selectedIndex = lines.findIndex((line) => line.line_code === startingLineCode); if (selectedIndex < 0) lines.unshift(startingLine); - return lines.map(l => { + return lines.map((l) => { const { line_code, type, old_line, new_line } = l; return { value: { line_code, type, old_line, new_line }, @@ -103,7 +103,7 @@ export function getCommentedLines(selectedCommentPosition, diffLines) { }; } - const findLineCodeIndex = line => position => { + const findLineCodeIndex = (line) => (position) => { return [position.line_code, position.left?.line_code, position.right?.line_code].includes( line.line_code, ); diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index fc131f548b4..b85cfa83e09 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -206,14 +206,14 @@ export default { const { project_id, iid } = this.getNoteableData; if (this.isUserAssigned) { - assignees = assignees.filter(assignee => assignee.id !== this.author.id); + assignees = assignees.filter((assignee) => assignee.id !== this.author.id); } else { assignees.push({ id: this.author.id }); } if (this.targetType === 'issue') { Api.updateIssue(project_id, iid, { - assignee_ids: assignees.map(assignee => assignee.id), + assignee_ids: assignees.map((assignee) => assignee.id), }) .then(() => this.handleAssigneeUpdate(assignees)) .catch(() => flash(__('Something went wrong while updating assignees'))); diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index 65b89b94eaa..8855ceac3d5 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -52,8 +52,9 @@ export default { return this.getDiscussion(this.note.discussion_id); }, ...mapState({ - batchSuggestionsInfo: state => state.notes.batchSuggestionsInfo, + batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo, }), + ...mapState('diffs', ['defaultSuggestionCommitMessage']), noteBody() { return this.note.note; }, @@ -98,12 +99,16 @@ export default { formCancelHandler(shouldConfirm, isDirty) { this.$emit('cancelForm', shouldConfirm, isDirty); }, - applySuggestion({ suggestionId, flashContainer, callback = () => {} }) { + applySuggestion({ suggestionId, flashContainer, callback = () => {}, message }) { const { discussion_id: discussionId, id: noteId } = this.note; - return this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer }).then( - callback, - ); + return this.submitSuggestion({ + discussionId, + noteId, + suggestionId, + flashContainer, + message, + }).then(callback); }, applySuggestionBatch({ flashContainer }) { return this.submitSuggestionBatch({ flashContainer }); @@ -130,6 +135,7 @@ export default { :note-html="note.note_html" :line-type="lineType" :help-page-path="helpPagePath" + :default-commit-message="defaultSuggestionCommitMessage" @apply="applySuggestion" @applyBatch="applySuggestionBatch" @addToBatch="addSuggestionToBatch" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 84769bfc7c8..9acb837c27f 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -3,20 +3,21 @@ import { mapGetters, mapActions, mapState } from 'vuex'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import eventHub from '../event_hub'; -import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue'; -import markdownField from '../../vue_shared/components/markdown/field.vue'; +import markdownField from '~/vue_shared/components/markdown/field.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import issuableStateMixin from '../mixins/issuable_state'; import resolvable from '../mixins/resolvable'; import { __, sprintf } from '~/locale'; import { getDraft, updateDraft } from '~/lib/utils/autosave'; +import CommentFieldLayout from './comment_field_layout.vue'; export default { name: 'NoteForm', components: { - NoteableWarning, markdownField, + CommentFieldLayout, }, - mixins: [issuableStateMixin, resolvable], + mixins: [glFeatureFlagsMixin(), issuableStateMixin, resolvable], props: { noteBody: { type: String, @@ -114,7 +115,7 @@ export default { 'getUserDataByProp', ]), ...mapState({ - withBatchComments: state => state.batchComments?.withBatchComments, + withBatchComments: (state) => state.batchComments?.withBatchComments, }), ...mapGetters('batchComments', ['hasDrafts']), showBatchCommentsActions() { @@ -125,8 +126,8 @@ export default { return ( this.discussion?.notes - .filter(n => n.resolvable) - .some(n => n.current_user?.can_resolve_discussion) || this.isDraft + .filter((n) => n.resolvable) + .some((n) => n.current_user?.can_resolve_discussion) || this.isDraft ); }, noteHash() { @@ -192,8 +193,7 @@ export default { }, canSuggest() { return ( - this.getNoteableData.can_receive_suggestion && - (this.line && this.line.can_receive_suggestion) + this.getNoteableData.can_receive_suggestion && this.line && this.line.can_receive_suggestion ); }, changedCommentText() { @@ -303,6 +303,9 @@ export default { this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve); }, + hasEmailParticipants() { + return this.getNoteableData.issue_email_participants?.length; + }, }, }; </script> @@ -316,46 +319,41 @@ export default { ></div> <div class="flash-container timeline-content"></div> <form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form"> - <noteable-warning - v-if="hasWarning(getNoteableData)" - :is-locked="isLocked(getNoteableData)" - :is-confidential="isConfidential(getNoteableData)" - :locked-noteable-docs-path="lockedIssueDocsPath" - :confidential-noteable-docs-path="confidentialIssueDocsPath" - /> - - <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" - @handleSuggestDismissed="() => $emit('handleSuggestDismissed')" - > - <textarea - id="note_note" - ref="textarea" - slot="textarea" - v-model="updatedNoteBody" - :data-supports-quick-actions="!isEditing" - 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" - :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> + <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" + @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" + :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> + </template> + </markdown-field> + </comment-field-layout> <div class="note-form-actions clearfix"> <template v-if="showBatchCommentsActions"> <p v-if="showResolveDiscussionToggle"> diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 62ee7f30c57..0a9a3da6069 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -201,14 +201,14 @@ export default { }; this.saveNote(replyData) - .then(res => { + .then((res) => { if (res.hasFlash !== true) { this.isReplying = false; clearDraft(this.autosaveKey); } callback(); }) - .catch(err => { + .catch((err) => { this.removePlaceholderNotes(); const msg = __( 'Your comment could not be submitted! Please check your network connection and try again.', diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 5073922e4a4..eaa64cf7c01 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -296,7 +296,7 @@ export default { this.updateSuccess(); callback(); }) - .catch(response => { + .catch((response) => { if (response.status === httpStatusCodes.GONE) { this.removeNote(this.note); this.updateSuccess(); diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 9eaa4e422d5..e9e687a8743 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -130,7 +130,7 @@ export default { const { parentElement } = this.$el; if (parentElement && parentElement.classList.contains('js-vue-notes-event')) { - parentElement.addEventListener('toggleAward', event => { + parentElement.addEventListener('toggleAward', (event) => { const { awardName, noteId } = event.detail; this.toggleAward({ awardName, noteId }); }); @@ -217,7 +217,7 @@ export default { const noteId = hash && hash.replace(/^note_/, ''); if (noteId) { - const discussion = this.discussions.find(d => d.notes.some(({ id }) => id === noteId)); + const discussion = this.discussions.find((d) => d.notes.some(({ id }) => id === noteId)); if (discussion) { this.expandDiscussion({ discussionId: discussion.id }); diff --git a/app/assets/javascripts/notes/components/timeline_toggle.vue b/app/assets/javascripts/notes/components/timeline_toggle.vue index d1ffe0a3601..8162878f80d 100644 --- a/app/assets/javascripts/notes/components/timeline_toggle.vue +++ b/app/assets/javascripts/notes/components/timeline_toggle.vue @@ -50,7 +50,6 @@ export default { v-gl-tooltip v-track-event="trackToggleTimelineView(timelineEnabled)" icon="comments" - size="small" :selected="timelineEnabled" :title="tooltip" :aria-label="tooltip" diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index 0628e1d8647..ab7fa793bdc 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -26,9 +26,9 @@ export default { return this.replies[this.replies.length - 1]; }, uniqueAuthors() { - const authors = this.replies.map(reply => reply.author || {}); + const authors = this.replies.map((reply) => reply.author || {}); - return uniqBy(authors, author => author.username); + return uniqBy(authors, (author) => author.username); }, className() { return this.collapsed ? 'collapsed' : 'expanded'; diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js index cdf9a46c5aa..7c9e7703d59 100644 --- a/app/assets/javascripts/notes/discussion_filters.js +++ b/app/assets/javascripts/notes/discussion_filters.js @@ -1,13 +1,13 @@ import Vue from 'vue'; import DiscussionFilter from './components/discussion_filter.vue'; -export default store => { +export default (store) => { const discussionFilterEl = document.getElementById('js-vue-discussion-filter'); if (discussionFilterEl) { const { defaultFilter, notesFilters } = discussionFilterEl.dataset; const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; - const filters = Object.keys(filterValues).map(entry => ({ + const filters = Object.keys(filterValues).map((entry) => ({ title: entry, value: filterValues[entry], })); diff --git a/app/assets/javascripts/notes/mixins/diff_line_note_form.js b/app/assets/javascripts/notes/mixins/diff_line_note_form.js index c4a42eb1a98..5ce541781d4 100644 --- a/app/assets/javascripts/notes/mixins/diff_line_note_form.js +++ b/app/assets/javascripts/notes/mixins/diff_line_note_form.js @@ -9,9 +9,9 @@ import { formatLineRange } from '~/notes/components/multiline_comment_utils'; export default { computed: { ...mapState({ - noteableData: state => state.notes.noteableData, - notesData: state => state.notes.notesData, - withBatchComments: state => state.batchComments?.withBatchComments, + noteableData: (state) => state.notes.noteableData, + notesData: (state) => state.notes.notesData, + withBatchComments: (state) => state.batchComments?.withBatchComments, }), ...mapGetters('diffs', ['getDiffFileByHash']), ...mapGetters('batchComments', ['shouldRenderDraftRowInDiscussion', 'draftForDiscussion']), diff --git a/app/assets/javascripts/notes/mixins/discussion_navigation.js b/app/assets/javascripts/notes/mixins/discussion_navigation.js index c6932bfacae..96974c4fa2d 100644 --- a/app/assets/javascripts/notes/mixins/discussion_navigation.js +++ b/app/assets/javascripts/notes/mixins/discussion_navigation.js @@ -99,7 +99,7 @@ export default { 'getDiscussion', ]), ...mapState({ - currentDiscussionId: state => state.notes.currentDiscussionId, + currentDiscussionId: (state) => state.notes.currentDiscussionId, }), }, methods: { diff --git a/app/assets/javascripts/notes/mixins/issuable_state.js b/app/assets/javascripts/notes/mixins/issuable_state.js index 0ca8c8c98a3..52b67764b70 100644 --- a/app/assets/javascripts/notes/mixins/issuable_state.js +++ b/app/assets/javascripts/notes/mixins/issuable_state.js @@ -12,21 +12,10 @@ export default { lockedIssueDocsPath() { return this.getNoteableDataByProp('locked_discussion_docs_path'); }, - confidentialIssueDocsPath() { - return this.getNoteableDataByProp('confidential_issues_docs_path'); - }, }, methods: { - isConfidential(issue) { - return Boolean(issue.confidential); - }, - isLocked(issue) { return Boolean(issue.discussion_locked); }, - - hasWarning(issue) { - return this.isConfidential(issue) || this.isLocked(issue); - }, }, }; diff --git a/app/assets/javascripts/notes/mixins/resolvable.js b/app/assets/javascripts/notes/mixins/resolvable.js index cef4475ed1d..baada4c5ce8 100644 --- a/app/assets/javascripts/notes/mixins/resolvable.js +++ b/app/assets/javascripts/notes/mixins/resolvable.js @@ -15,7 +15,7 @@ export default { if (notes) { // Decide resolved state using store. Only valid for discussions. - return notes.filter(note => !note.system).every(note => note.resolved); + return notes.filter((note) => !note.system).every((note) => note.resolved); } return resolved; diff --git a/app/assets/javascripts/notes/sort_discussions.js b/app/assets/javascripts/notes/sort_discussions.js index a06c23f5f76..ecfa3223039 100644 --- a/app/assets/javascripts/notes/sort_discussions.js +++ b/app/assets/javascripts/notes/sort_discussions.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import SortDiscussion from './components/sort_discussion.vue'; -export default store => { +export default (store) => { const el = document.getElementById('js-vue-sort-issue-discussions'); if (!el) return null; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 1fe5d6c2955..c6684efed4d 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -141,7 +141,7 @@ export const updateNote = ({ commit, dispatch }, { endpoint, note }) => export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => { const { notesById } = getters; - const debouncedFetchDiscussions = isFetching => { + const debouncedFetchDiscussions = (isFetching) => { if (!isFetching) { commit(types.SET_FETCHING_DISCUSSIONS, true); dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); @@ -159,7 +159,7 @@ export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) } }; - notes.forEach(note => { + notes.forEach((note) => { if (notesById[note.id]) { commit(types.UPDATE_NOTE, note); } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { @@ -329,7 +329,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { } } - const processQuickActions = res => { + const processQuickActions = (res) => { const { errors: { commands_only: message } = { commands_only: null } } = res; /* The following reply means that quick actions have been successfully applied: @@ -347,7 +347,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { return res; }; - const processEmojiAward = res => { + const processEmojiAward = (res) => { const { commands_changes: commandsChanges } = res; const { emoji_award: emojiAward } = commandsChanges || {}; if (!emojiAward) { @@ -357,7 +357,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { const votesBlock = $('.js-awards-block').eq(0); return loadAwardsHandler() - .then(awardsHandler => { + .then((awardsHandler) => { awardsHandler.addAwardToEmojiBar(votesBlock, emojiAward); awardsHandler.scrollToAwards(); }) @@ -371,7 +371,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { .then(() => res); }; - const processTimeTracking = res => { + const processTimeTracking = (res) => { const { commands_changes: commandsChanges } = res; const { spend_time: spendTime, time_estimate: timeEstimate } = commandsChanges || {}; if (spendTime != null || timeEstimate != null) { @@ -383,7 +383,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { return res; }; - const removePlaceholder = res => { + const removePlaceholder = (res) => { if (replyId) { commit(types.REMOVE_PLACEHOLDER_NOTES); } @@ -391,7 +391,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { return res; }; - const processErrors = error => { + const processErrors = (error) => { if (error.response) { const { response: { data = {} }, @@ -435,7 +435,7 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { return resp; }; -const getFetchDataParams = state => { +const getFetchDataParams = (state) => { const endpoint = state.notesData.notesPath; const options = { headers: { @@ -559,7 +559,7 @@ export const updateResolvableDiscussionsCounts = ({ commit }) => export const submitSuggestion = ( { commit, dispatch }, - { discussionId, noteId, suggestionId, flashContainer }, + { discussionId, suggestionId, flashContainer, message }, ) => { const dispatchResolveDiscussion = () => dispatch('resolveDiscussion', { discussionId }).catch(() => {}); @@ -567,10 +567,9 @@ export const submitSuggestion = ( commit(types.SET_RESOLVING_DISCUSSION, true); dispatch('stopPolling'); - return Api.applySuggestion(suggestionId) - .then(() => commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId })) + return Api.applySuggestion(suggestionId, message) .then(dispatchResolveDiscussion) - .catch(err => { + .catch((err) => { const defaultMessage = __( 'Something went wrong while applying the suggestion. Please try again.', ); @@ -590,13 +589,8 @@ export const submitSuggestion = ( export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContainer }) => { const suggestionIds = state.batchSuggestionsInfo.map(({ suggestionId }) => suggestionId); - const applyAllSuggestions = () => - state.batchSuggestionsInfo.map(suggestionInfo => - commit(types.APPLY_SUGGESTION, suggestionInfo), - ); - const resolveAllDiscussions = () => - state.batchSuggestionsInfo.map(suggestionInfo => { + state.batchSuggestionsInfo.map((suggestionInfo) => { const { discussionId } = suggestionInfo; return dispatch('resolveDiscussion', { discussionId }).catch(() => {}); }); @@ -606,10 +600,9 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContai dispatch('stopPolling'); return Api.applySuggestionBatch(suggestionIds) - .then(() => Promise.all(applyAllSuggestions())) .then(() => Promise.all(resolveAllDiscussions())) .then(() => commit(types.CLEAR_SUGGESTION_BATCH)) - .catch(err => { + .catch((err) => { const defaultMessage = __( 'Something went wrong while applying the batch of suggestions. Please try again.', ); @@ -652,10 +645,10 @@ export const fetchDescriptionVersion = ({ dispatch }, { endpoint, startingVersio return axios .get(requestUrl) - .then(res => { + .then((res) => { dispatch('receiveDescriptionVersion', { descriptionVersion: res.data, versionId }); }) - .catch(error => { + .catch((error) => { dispatch('receiveDescriptionVersionError', error); Flash(__('Something went wrong while fetching description changes. Please try again.')); }); @@ -687,7 +680,7 @@ export const softDeleteDescriptionVersion = ( .then(() => { dispatch('receiveDeleteDescriptionVersion', versionId); }) - .catch(error => { + .catch((error) => { dispatch('receiveDeleteDescriptionVersionError', error); Flash(__('Something went wrong while deleting description changes. Please try again.')); diff --git a/app/assets/javascripts/notes/stores/collapse_utils.js b/app/assets/javascripts/notes/stores/collapse_utils.js index f34247d4eb0..b2e2f6e2c31 100644 --- a/app/assets/javascripts/notes/stores/collapse_utils.js +++ b/app/assets/javascripts/notes/stores/collapse_utils.js @@ -18,7 +18,7 @@ export const getTimeDifferenceMinutes = (noteBeggining, noteEnd) => { * @param {Object} note * @returns {Boolean} */ -export const isDescriptionSystemNote = note => note.system && note.note === DESCRIPTION_TYPE; +export const isDescriptionSystemNote = (note) => note.system && note.note === DESCRIPTION_TYPE; /** * Collapses the system notes of a description type, e.g. Changed the description, n minutes ago @@ -29,7 +29,7 @@ export const isDescriptionSystemNote = note => note.system && note.note === DESC * @param {Array} notes * @returns {Array} */ -export const collapseSystemNotes = notes => { +export const collapseSystemNotes = (notes) => { let lastDescriptionSystemNote = null; let lastDescriptionSystemNoteIndex = -1; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 5b3ffa425a0..5891a2e63e3 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -2,14 +2,14 @@ import { flattenDeep, clone } from 'lodash'; import * as constants from '../constants'; import { collapseSystemNotes } from './collapse_utils'; -export const discussions = state => { +export const discussions = (state) => { let discussionsInState = clone(state.discussions); // NOTE: not testing bc will be removed when backend is finished. if (state.isTimelineEnabled) { discussionsInState = discussionsInState .reduce((acc, discussion) => { - const transformedToIndividualNotes = discussion.notes.map(note => ({ + const transformedToIndividualNotes = discussion.notes.map((note) => ({ ...discussion, id: note.id, created_at: note.created_at, @@ -29,52 +29,52 @@ export const discussions = state => { return collapseSystemNotes(discussionsInState); }; -export const convertedDisscussionIds = state => state.convertedDisscussionIds; +export const convertedDisscussionIds = (state) => state.convertedDisscussionIds; -export const targetNoteHash = state => state.targetNoteHash; +export const targetNoteHash = (state) => state.targetNoteHash; -export const getNotesData = state => state.notesData; +export const getNotesData = (state) => state.notesData; -export const isNotesFetched = state => state.isNotesFetched; +export const isNotesFetched = (state) => state.isNotesFetched; /* * WARNING: This is an example of an "unnecessary" getter * more info found here: https://gitlab.com/groups/gitlab-org/-/epics/2913. */ -export const sortDirection = state => state.discussionSortOrder; +export const sortDirection = (state) => state.discussionSortOrder; -export const persistSortOrder = state => state.persistSortOrder; +export const persistSortOrder = (state) => state.persistSortOrder; -export const timelineEnabled = state => state.isTimelineEnabled; +export const timelineEnabled = (state) => state.isTimelineEnabled; -export const isLoading = state => state.isLoading; +export const isLoading = (state) => state.isLoading; -export const getNotesDataByProp = state => prop => state.notesData[prop]; +export const getNotesDataByProp = (state) => (prop) => state.notesData[prop]; -export const getNoteableData = state => state.noteableData; +export const getNoteableData = (state) => state.noteableData; -export const getNoteableDataByProp = state => prop => state.noteableData[prop]; +export const getNoteableDataByProp = (state) => (prop) => state.noteableData[prop]; -export const getBlockedByIssues = state => state.noteableData.blocked_by_issues; +export const getBlockedByIssues = (state) => state.noteableData.blocked_by_issues; -export const userCanReply = state => Boolean(state.noteableData.current_user.can_create_note); +export const userCanReply = (state) => Boolean(state.noteableData.current_user.can_create_note); -export const openState = state => state.noteableData.state; +export const openState = (state) => state.noteableData.state; -export const getUserData = state => state.userData || {}; +export const getUserData = (state) => state.userData || {}; -export const getUserDataByProp = state => prop => state.userData && state.userData[prop]; +export const getUserDataByProp = (state) => (prop) => state.userData && state.userData[prop]; -export const descriptionVersions = state => state.descriptionVersions; +export const descriptionVersions = (state) => state.descriptionVersions; -export const notesById = state => +export const notesById = (state) => state.discussions.reduce((acc, note) => { - note.notes.every(n => Object.assign(acc, { [n.id]: n })); + note.notes.every((n) => Object.assign(acc, { [n.id]: n })); return acc; }, {}); -export const noteableType = state => { +export const noteableType = (state) => { const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants; if (state.noteableData.noteableType === EPIC_NOTEABLE_TYPE) { @@ -84,21 +84,21 @@ export const noteableType = state => { return state.noteableData.merge_params ? MERGE_REQUEST_NOTEABLE_TYPE : ISSUE_NOTEABLE_TYPE; }; -const reverseNotes = array => array.slice(0).reverse(); +const reverseNotes = (array) => array.slice(0).reverse(); const isLastNote = (note, state) => !note.system && state.userData && note.author && note.author.id === state.userData.id; -export const getCurrentUserLastNote = state => - flattenDeep(reverseNotes(state.discussions).map(note => reverseNotes(note.notes))).find(el => +export const getCurrentUserLastNote = (state) => + flattenDeep(reverseNotes(state.discussions).map((note) => reverseNotes(note.notes))).find((el) => isLastNote(el, state), ); -export const getDiscussionLastNote = state => discussion => - reverseNotes(discussion.notes).find(el => isLastNote(el, state)); +export const getDiscussionLastNote = (state) => (discussion) => + reverseNotes(discussion.notes).find((el) => isLastNote(el, state)); -export const unresolvedDiscussionsCount = state => state.unresolvedDiscussionsCount; -export const resolvableDiscussionsCount = state => state.resolvableDiscussionsCount; +export const unresolvedDiscussionsCount = (state) => state.unresolvedDiscussionsCount; +export const resolvableDiscussionsCount = (state) => state.resolvableDiscussionsCount; export const showJumpToNextDiscussion = (state, getters) => (mode = 'discussion') => { const orderedDiffs = @@ -109,20 +109,20 @@ export const showJumpToNextDiscussion = (state, getters) => (mode = 'discussion' return orderedDiffs.length > 1; }; -export const isDiscussionResolved = (state, getters) => discussionId => +export const isDiscussionResolved = (state, getters) => (discussionId) => getters.resolvedDiscussionsById[discussionId] !== undefined; -export const allResolvableDiscussions = state => - state.discussions.filter(d => !d.individual_note && d.resolvable); +export const allResolvableDiscussions = (state) => + state.discussions.filter((d) => !d.individual_note && d.resolvable); -export const resolvedDiscussionsById = state => { +export const resolvedDiscussionsById = (state) => { const map = {}; state.discussions - .filter(d => d.resolvable) - .forEach(n => { + .filter((d) => d.resolvable) + .forEach((n) => { if (n.notes) { - const resolved = n.notes.filter(note => note.resolvable).every(note => note.resolved); + const resolved = n.notes.filter((note) => note.resolvable).every((note) => note.resolved); if (resolved) { map[n.id] = n; @@ -136,7 +136,7 @@ export const resolvedDiscussionsById = state => { // Gets Discussions IDs ordered by the date of their initial note export const unresolvedDiscussionsIdsByDate = (state, getters) => getters.allResolvableDiscussions - .filter(d => !d.resolved) + .filter((d) => !d.resolved) .sort((a, b) => { const aDate = new Date(a.notes[0].created_at); const bDate = new Date(b.notes[0].created_at); @@ -147,7 +147,7 @@ export const unresolvedDiscussionsIdsByDate = (state, getters) => return aDate === bDate ? 0 : 1; }) - .map(d => d.id); + .map((d) => d.id); // Gets Discussions IDs ordered by their position in the diff // @@ -156,7 +156,7 @@ export const unresolvedDiscussionsIdsByDate = (state, getters) => // line numbers. export const unresolvedDiscussionsIdsByDiff = (state, getters) => getters.allResolvableDiscussions - .filter(d => !d.resolved && d.active) + .filter((d) => !d.resolved && d.active) .sort((a, b) => { if (!a.diff_file || !b.diff_file) { return 0; @@ -176,7 +176,7 @@ export const unresolvedDiscussionsIdsByDiff = (state, getters) => ? -1 : 1; }) - .map(d => d.id); + .map((d) => d.id); export const resolvedDiscussionCount = (state, getters) => { const resolvedMap = getters.resolvedDiscussionsById; @@ -184,16 +184,16 @@ export const resolvedDiscussionCount = (state, getters) => { return Object.keys(resolvedMap).length; }; -export const discussionTabCounter = state => +export const discussionTabCounter = (state) => state.discussions.reduce( (acc, discussion) => - acc + discussion.notes.filter(note => !note.system && !note.placeholder).length, + acc + discussion.notes.filter((note) => !note.system && !note.placeholder).length, 0, ); // Returns the list of discussion IDs ordered according to given parameter // @param {Boolean} diffOrder - is ordered by diff? -export const unresolvedDiscussionsIdsOrdered = (state, getters) => diffOrder => { +export const unresolvedDiscussionsIdsOrdered = (state, getters) => (diffOrder) => { if (diffOrder) { return getters.unresolvedDiscussionsIdsByDiff; } @@ -241,17 +241,17 @@ export const previousUnresolvedDiscussionId = (state, getters) => (discussionId, getters.findUnresolvedDiscussionIdNeighbor({ discussionId, diffOrder, step: -1 }); // @param {Boolean} diffOrder - is ordered by diff? -export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => { +export const firstUnresolvedDiscussionId = (state, getters) => (diffOrder) => { if (diffOrder) { return getters.unresolvedDiscussionsIdsByDiff[0]; } return getters.unresolvedDiscussionsIdsByDate[0]; }; -export const getDiscussion = state => discussionId => - state.discussions.find(discussion => discussion.id === discussionId); +export const getDiscussion = (state) => (discussionId) => + state.discussions.find((discussion) => discussion.id === discussionId); -export const commentsDisabled = state => state.commentsDisabled; +export const commentsDisabled = (state) => state.commentsDisabled; export const suggestionsCount = (state, getters) => - Object.values(getters.notesById).filter(n => n.suggestions.length).length; + Object.values(getters.notesById).filter((n) => n.suggestions.length).length; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 4421a84a6b1..144a3d7ba90 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -15,7 +15,7 @@ export default () => ({ batchSuggestionsInfo: [], currentlyFetchingDiscussions: false, /** - * selectedCommentPosition & selectedCommentPosition structures are the same as `position.line_range`: + * selectedCommentPosition & selectedCommentPositionHover structures are the same as `position.line_range`: * { * start: { line_code: string, new_line: number, old_line:number, type: string }, * end: { line_code: string, new_line: number, old_line:number, type: string }, diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 53387b2eaff..2c51ce0d970 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -7,7 +7,7 @@ export default { [types.ADD_NEW_NOTE](state, data) { const note = data.discussion ? data.discussion.notes[0] : data; const { discussion_id, type } = note; - const [exists] = state.discussions.filter(n => n.id === note.discussion_id); + const [exists] = state.discussions.filter((n) => n.id === note.discussion_id); const isDiscussion = type === constants.DISCUSSION_NOTE || type === constants.DIFF_NOTE; if (!exists) { @@ -128,7 +128,7 @@ export default { // To support legacy notes, should be very rare case. if (discussion.individual_note && discussion.notes.length > 1) { - discussion.notes.forEach(n => { + discussion.notes.forEach((n) => { acc.push({ ...discussion, ...diffData, @@ -183,7 +183,7 @@ export default { const { id, name, username } = state.userData; const hasEmojiAwardedByCurrentUser = note.award_emoji.filter( - emoji => `${emoji.name}` === `${data.awardName}` && emoji.user.id === id, + (emoji) => `${emoji.name}` === `${data.awardName}` && emoji.user.id === id, ); if (hasEmojiAwardedByCurrentUser.length) { @@ -206,7 +206,7 @@ export default { [types.SET_EXPAND_DISCUSSIONS](state, { discussionIds, expanded }) { if (discussionIds?.length) { - discussionIds.forEach(discussionId => { + discussionIds.forEach((discussionId) => { const discussion = utils.findNoteObjectById(state.discussions, discussionId); Object.assign(discussion, { expanded }); }); @@ -236,7 +236,7 @@ export default { const noteObj = utils.findNoteObjectById(state.discussions, discussionId); const comment = utils.findNoteObjectById(noteObj.notes, noteId); - comment.suggestions = comment.suggestions.map(suggestion => ({ + comment.suggestions = comment.suggestions.map((suggestion) => ({ ...suggestion, applied: suggestion.applied || suggestion.id === suggestionId, appliable: false, @@ -244,13 +244,13 @@ export default { }, [types.SET_APPLYING_BATCH_STATE](state, isApplyingBatch) { - state.batchSuggestionsInfo.forEach(suggestionInfo => { + state.batchSuggestionsInfo.forEach((suggestionInfo) => { const { discussionId, noteId, suggestionId } = suggestionInfo; const noteObj = utils.findNoteObjectById(state.discussions, discussionId); const comment = utils.findNoteObjectById(noteObj.notes, noteId); - comment.suggestions = comment.suggestions.map(suggestion => ({ + comment.suggestions = comment.suggestions.map((suggestion) => ({ ...suggestion, is_applying_batch: suggestion.id === suggestionId && isApplyingBatch, })); @@ -278,7 +278,7 @@ export default { [types.UPDATE_DISCUSSION](state, noteData) { const note = noteData; - const selectedDiscussion = state.discussions.find(disc => disc.id === note.id); + const selectedDiscussion = state.discussions.find((disc) => disc.id === note.id); note.expanded = true; // override expand flag to prevent collapse if (note.diff_file) { Object.assign(note, { @@ -289,7 +289,7 @@ export default { }, [types.UPDATE_DISCUSSION_POSITION](state, { discussionId, position }) { - const selectedDiscussion = state.discussions.find(disc => disc.id === discussionId); + const selectedDiscussion = state.discussions.find((disc) => disc.id === discussionId); if (selectedDiscussion) Object.assign(selectedDiscussion.position, { ...position }); }, @@ -341,13 +341,13 @@ export default { }, [types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS](state) { state.resolvableDiscussionsCount = state.discussions.filter( - discussion => !discussion.individual_note && discussion.resolvable, + (discussion) => !discussion.individual_note && discussion.resolvable, ).length; state.unresolvedDiscussionsCount = state.discussions.filter( - discussion => + (discussion) => !discussion.individual_note && discussion.resolvable && - discussion.notes.some(note => note.resolvable && !note.resolved), + discussion.notes.some((note) => note.resolvable && !note.resolved), ).length; }, diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index 10faac0c32b..6df926e1249 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -6,13 +6,13 @@ import createGqClient, { fetchPolicies } from '~/lib/graphql'; // factory function because global flag makes RegExp stateful const createQuickActionsRegex = () => /^\/\w+.*$/gm; -export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0]; +export const findNoteObjectById = (notes, id) => notes.filter((n) => n.id === id)[0]; -export const getQuickActionText = note => { +export const getQuickActionText = (note) => { let text = __('Applying command'); const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || []; - const executedCommands = quickActions.filter(command => { + const executedCommands = quickActions.filter((command) => { const commandRegex = new RegExp(`/${command.name}`); return commandRegex.test(note); }); @@ -29,12 +29,12 @@ export const getQuickActionText = note => { return text; }; -export const hasQuickActions = note => createQuickActionsRegex().test(note); +export const hasQuickActions = (note) => createQuickActionsRegex().test(note); -export const stripQuickActions = note => note.replace(createQuickActionsRegex(), '').trim(); +export const stripQuickActions = (note) => note.replace(createQuickActionsRegex(), '').trim(); -export const prepareDiffLines = diffLines => - diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) })); +export const prepareDiffLines = (diffLines) => + diffLines.map((line) => ({ ...trimFirstCharOfLineContent(line) })); export const gqClient = createGqClient( {}, diff --git a/app/assets/javascripts/notes/utils.js b/app/assets/javascripts/notes/utils.js index e6c2eb06a51..7966a884eab 100644 --- a/app/assets/javascripts/notes/utils.js +++ b/app/assets/javascripts/notes/utils.js @@ -4,7 +4,7 @@ * Tracks snowplow event when User toggles timeline view * @param {Boolean} enabled that will be send as a property for the event */ -export const trackToggleTimelineView = enabled => ({ +export const trackToggleTimelineView = (enabled) => ({ category: 'Incident Management', action: 'toggle_incident_comments_into_timeline_view', label: 'Status', |