Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/notes')
-rw-r--r--app/assets/javascripts/notes/components/comment_field_layout.vue2
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue90
-rw-r--r--app/assets/javascripts/notes/components/diff_discussion_header.vue6
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue4
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_navigator.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue14
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue11
-rw-r--r--app/assets/javascripts/notes/components/email_participants_warning.vue2
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue117
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue29
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue38
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue91
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue30
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue63
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue24
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue47
-rw-r--r--app/assets/javascripts/notes/components/sort_discussion.vue2
-rw-r--r--app/assets/javascripts/notes/components/timeline_toggle.vue2
-rw-r--r--app/assets/javascripts/notes/components/toggle_replies_widget.vue25
-rw-r--r--app/assets/javascripts/notes/index.js2
-rw-r--r--app/assets/javascripts/notes/mixins/autosave.js2
-rw-r--r--app/assets/javascripts/notes/mixins/diff_line_note_form.js2
-rw-r--r--app/assets/javascripts/notes/stores/actions.js50
-rw-r--r--app/assets/javascripts/notes/stores/getters.js2
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js3
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js38
-rw-r--r--app/assets/javascripts/notes/stores/utils.js4
33 files changed, 402 insertions, 311 deletions
diff --git a/app/assets/javascripts/notes/components/comment_field_layout.vue b/app/assets/javascripts/notes/components/comment_field_layout.vue
index aaf64702ffd..47d14783d5d 100644
--- a/app/assets/javascripts/notes/components/comment_field_layout.vue
+++ b/app/assets/javascripts/notes/components/comment_field_layout.vue
@@ -1,6 +1,6 @@
<script>
-import EmailParticipantsWarning from './email_participants_warning.vue';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
+import EmailParticipantsWarning from './email_participants_warning.vue';
const DEFAULT_NOTEABLE_TYPE = 'Issue';
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 111af977ec5..50db3b86025 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -1,29 +1,27 @@
<script>
+import { GlButton, GlIcon, GlFormCheckbox, GlTooltipDirective } from '@gitlab/ui';
+import Autosize from 'autosize';
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
-import { isEmpty } from 'lodash';
-import Autosize from 'autosize';
-import { GlButton, GlIcon } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
-import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-import { deprecatedCreateFlash as Flash } from '~/flash';
import Autosave from '~/autosave';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
+import { deprecatedCreateFlash as Flash } from '~/flash';
import {
capitalizeFirstCharacter,
convertToCamelCase,
splitCamelCase,
slugifyWithUnderscore,
} from '~/lib/utils/text_utility';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import * as constants from '../constants';
-import eventHub from '../event_hub';
+import { __, sprintf } from '~/locale';
import markdownField from '~/vue_shared/components/markdown/field.vue';
-import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.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 * as constants from '../constants';
+import eventHub from '../event_hub';
import issuableStateMixin from '../mixins/issuable_state';
import CommentFieldLayout from './comment_field_layout.vue';
+import discussionLockedWidget from './discussion_locked_widget.vue';
+import noteSignedOutWidget from './note_signed_out_widget.vue';
export default {
name: 'CommentForm',
@@ -31,11 +29,14 @@ export default {
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
- userAvatarLink,
GlButton,
TimelineEntryItem,
GlIcon,
CommentFieldLayout,
+ GlFormCheckbox,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin(), issuableStateMixin],
props: {
@@ -48,8 +49,8 @@ export default {
return {
note: '',
noteType: constants.COMMENT,
+ noteIsConfidential: false,
isSubmitting: false,
- isSubmitButtonDisabled: true,
};
},
computed: {
@@ -82,6 +83,9 @@ export default {
canCreateNote() {
return this.getNoteableData.current_user.can_create_note;
},
+ canSetConfidential() {
+ return this.getNoteableData.current_user.can_update;
+ },
issueActionButtonTitle() {
const openOrClose = this.isOpen ? 'close' : 'reopen';
@@ -145,13 +149,14 @@ export default {
trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
},
- },
- watch: {
- note(newNote) {
- this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
+ hasCloseAndCommentButton() {
+ return !this.glFeatures.removeCommentCloseReopen;
},
- isSubmitting(newValue) {
- this.setIsSubmitButtonDisabled(this.note, newValue);
+ confidentialNotesEnabled() {
+ return Boolean(this.glFeatures.confidentialNotes);
+ },
+ disableSubmitButton() {
+ return this.note.length === 0 || this.isSubmitting;
},
},
mounted() {
@@ -172,13 +177,6 @@ export default {
'reopenIssuable',
'toggleIssueLocalState',
]),
- setIsSubmitButtonDisabled(note, isSubmitting) {
- if (!isEmpty(note) && !isSubmitting) {
- this.isSubmitButtonDisabled = false;
- } else {
- this.isSubmitButtonDisabled = true;
- }
- },
handleSave(withIssueAction) {
if (this.note.length) {
const noteData = {
@@ -188,6 +186,7 @@ export default {
note: {
noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id,
+ confidential: this.noteIsConfidential,
note: this.note,
},
merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
@@ -251,6 +250,7 @@ export default {
if (shouldClear) {
this.note = '';
+ this.noteIsConfidential = false;
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
@@ -301,15 +301,6 @@ export default {
<ul v-else-if="canCreateNote" class="notes notes-form timeline">
<timeline-entry-item class="note-form">
<div class="flash-container error-alert timeline-content"></div>
- <div class="timeline-icon d-none d-md-block">
- <user-avatar-link
- v-if="author"
- :link-href="author.path"
- :img-src="author.avatar_url"
- :img-alt="author.name"
- :img-size="40"
- />
- </div>
<div class="timeline-content timeline-content-form">
<form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form">
<comment-field-layout
@@ -348,11 +339,26 @@ export default {
</markdown-field>
</comment-field-layout>
<div class="note-form-actions">
+ <gl-form-checkbox
+ v-if="confidentialNotesEnabled && canSetConfidential"
+ v-model="noteIsConfidential"
+ class="gl-mb-6"
+ data-testid="confidential-note-checkbox"
+ >
+ {{ s__('Notes|Make this comment confidential') }}
+ <gl-icon
+ v-gl-tooltip:tooltipcontainer.bottom
+ name="question"
+ :size="16"
+ :title="s__('Notes|Confidential comments are only visible to project members')"
+ class="gl-text-gray-500"
+ />
+ </gl-form-checkbox>
<div
class="btn-group gl-mr-3 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<gl-button
- :disabled="isSubmitButtonDisabled"
+ :disabled="disableSubmitButton"
class="js-comment-button js-comment-submit-button"
data-qa-selector="comment_button"
data-testid="comment-button"
@@ -365,7 +371,7 @@ export default {
>{{ commentButtonTitle }}</gl-button
>
<gl-button
- :disabled="isSubmitButtonDisabled"
+ :disabled="disableSubmitButton"
name="button"
category="primary"
variant="success"
@@ -384,7 +390,7 @@ export default {
class="btn btn-transparent"
@click.prevent="setNoteType('comment')"
>
- <gl-icon name="check" class="icon" />
+ <gl-icon name="check" class="icon gl-flex-shrink-0" />
<div class="description">
<strong>{{ __('Comment') }}</strong>
<p>
@@ -400,10 +406,12 @@ export default {
<li class="divider droplab-item-ignore"></li>
<li :class="{ 'droplab-item-selected': noteType === 'discussion' }">
<button
+ type="button"
+ class="btn btn-transparent"
data-qa-selector="discussion_menu_item"
@click.prevent="setNoteType('discussion')"
>
- <gl-icon name="check" class="icon" />
+ <gl-icon name="check" class="icon gl-flex-shrink-0" />
<div class="description">
<strong>{{ __('Start thread') }}</strong>
<p>{{ startDiscussionDescription }}</p>
@@ -414,7 +422,7 @@ export default {
</div>
<gl-button
- v-if="canToggleIssueState"
+ v-if="hasCloseAndCommentButton && canToggleIssueState"
:loading="isToggleStateButtonLoading"
category="secondary"
:variant="buttonVariant"
diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue
index ee39a529345..0ce1eb8191a 100644
--- a/app/assets/javascripts/notes/components/diff_discussion_header.vue
+++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue
@@ -1,10 +1,10 @@
<script>
-import { mapActions } from 'vuex';
-import { escape } from 'lodash';
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { escape } from 'lodash';
+import { mapActions } from 'vuex';
-import { s__, __, sprintf } from '~/locale';
import { truncateSha } from '~/lib/utils/text_utility';
+import { s__, __, sprintf } from '~/locale';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteEditedText from './note_edited_text.vue';
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index b7355d4d927..e96e1204f76 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,12 +1,12 @@
<script>
/* eslint-disable vue/no-v-html */
-import { mapState, mapActions } from 'vuex';
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
+import { mapState, mapActions } from 'vuex';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
-import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { getDiffMode } from '~/diffs/store/utils';
import { diffViewerModes } from '~/ide/constants';
+import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import { isCollapsed } from '../../diffs/utils/diff_file';
const FIRST_CHAR_REGEX = /^(\+|-| )/;
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
index da4134ab2c4..27408bc3354 100644
--- a/app/assets/javascripts/notes/components/discussion_actions.vue
+++ b/app/assets/javascripts/notes/components/discussion_actions.vue
@@ -1,8 +1,8 @@
<script>
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ReplyPlaceholder from './discussion_reply_placeholder.vue';
import ResolveDiscussionButton from './discussion_resolve_button.vue';
import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'DiscussionActions',
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 0a72627834d..55cf75132a9 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,6 +1,6 @@
<script>
-import { mapGetters, mapActions } from 'vuex';
import { GlTooltipDirective, GlIcon, GlButton, GlButtonGroup } from '@gitlab/ui';
+import { mapGetters, mapActions } from 'vuex';
import { __ } from '~/locale';
import discussionNavigation from '../mixins/discussion_navigation';
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index aa61aa9b3cb..88f053aed67 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -1,6 +1,6 @@
<script>
-import { mapGetters, mapActions } from 'vuex';
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
+import { mapGetters, mapActions } from 'vuex';
import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
diff --git a/app/assets/javascripts/notes/components/discussion_navigator.vue b/app/assets/javascripts/notes/components/discussion_navigator.vue
index facc53e27a6..fa3c900c337 100644
--- a/app/assets/javascripts/notes/components/discussion_navigator.vue
+++ b/app/assets/javascripts/notes/components/discussion_navigator.vue
@@ -1,8 +1,8 @@
<script>
/* global Mousetrap */
import 'mousetrap';
-import discussionNavigation from '~/notes/mixins/discussion_navigation';
import eventHub from '~/notes/event_hub';
+import discussionNavigation from '~/notes/mixins/discussion_navigation';
export default {
mixins: [discussionNavigation],
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
index 8ac915c3c03..0f74d78c8e0 100644
--- a/app/assets/javascripts/notes/components/discussion_notes.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -1,15 +1,14 @@
<script>
import { mapGetters, mapActions } from 'vuex';
-import { SYSTEM_NOTE } from '../constants';
import { __ } from '~/locale';
import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
import SystemNote from '~/vue_shared/components/notes/system_note.vue';
+import { SYSTEM_NOTE } from '../constants';
+import DiscussionNotesRepliesWrapper from './discussion_notes_replies_wrapper.vue';
+import NoteEditedText from './note_edited_text.vue';
import NoteableNote from './noteable_note.vue';
import ToggleRepliesWidget from './toggle_replies_widget.vue';
-import NoteEditedText from './note_edited_text.vue';
-import DiscussionNotesRepliesWrapper from './discussion_notes_replies_wrapper.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'DiscussionNotes',
@@ -18,7 +17,6 @@ export default {
NoteEditedText,
DiscussionNotesRepliesWrapper,
},
- mixins: [glFeatureFlagsMixin()],
props: {
discussion: {
type: Object,
@@ -96,14 +94,14 @@ export default {
return note.isPlaceholderNote ? note.notes[0] : note;
},
handleMouseEnter(discussion) {
- if (this.glFeatures.multilineComments && discussion.position) {
+ if (discussion.position) {
this.setSelectedCommentPositionHover(discussion.position.line_range);
}
},
handleMouseLeave(discussion) {
- // Even though position isn't used here we still don't want to unecessarily call a mutation
+ // Even though position isn't used here we still don't want to unnecessarily call a mutation
// The lack of position tells us that highlighting is irrelevant in this context
- if (this.glFeatures.multilineComments && discussion.position) {
+ if (discussion.position) {
this.setSelectedCommentPositionHover();
}
},
diff --git a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
index 2ddca56ddd5..dfeda4aae7c 100644
--- a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
@@ -16,9 +16,14 @@ export default {
},
render(h, { props, children }) {
if (props.isDiffDiscussion) {
- return h('li', { class: 'discussion-collapsible bordered-box clearfix' }, [
- h('ul', { class: 'notes' }, children),
- ]);
+ return h(
+ 'li',
+ {
+ class:
+ 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base gl-overflow-hidden clearfix',
+ },
+ [h('ul', { class: 'notes' }, children)],
+ );
}
return children;
diff --git a/app/assets/javascripts/notes/components/email_participants_warning.vue b/app/assets/javascripts/notes/components/email_participants_warning.vue
index bb1ff58120a..ecf42fce1d2 100644
--- a/app/assets/javascripts/notes/components/email_participants_warning.vue
+++ b/app/assets/javascripts/notes/components/email_participants_warning.vue
@@ -1,7 +1,7 @@
<script>
import { GlSprintf } from '@gitlab/ui';
-import { s__, sprintf } from '~/locale';
import { toNounSeriesText } from '~/lib/utils/grammar';
+import { s__, sprintf } from '~/locale';
export default {
components: {
diff --git a/app/assets/javascripts/notes/components/multiline_comment_form.vue b/app/assets/javascripts/notes/components/multiline_comment_form.vue
index 9fbf2c9265c..6ad565567be 100644
--- a/app/assets/javascripts/notes/components/multiline_comment_form.vue
+++ b/app/assets/javascripts/notes/components/multiline_comment_form.vue
@@ -1,6 +1,6 @@
<script>
-import { mapActions, mapState } from 'vuex';
import { GlFormSelect, GlSprintf } from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
import { getSymbol, getLineClasses } from './multiline_comment_utils';
export default {
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index b85cfa83e09..907a4316a93 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,13 +1,14 @@
<script>
-import { mapGetters } from 'vuex';
import { GlTooltipDirective, GlIcon, GlButton, GlDropdownItem } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
-import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
-import ReplyButton from './note_actions/reply_button.vue';
-import eventHub from '~/sidebar/event_hub';
+import { mapGetters } from 'vuex';
import Api from '~/api';
+import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import { deprecatedCreateFlash as flash } from '~/flash';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
+import { __, sprintf } from '~/locale';
+import eventHub from '~/sidebar/event_hub';
import { splitCamelCase } from '../../lib/utils/text_utility';
+import ReplyButton from './note_actions/reply_button.vue';
export default {
name: 'NoteActions',
@@ -193,7 +194,7 @@ export default {
},
closeTooltip() {
this.$nextTick(() => {
- this.$root.$emit('bv::hide::tooltip');
+ this.$root.$emit(BV_HIDE_TOOLTIP);
});
},
handleAssigneeUpdate(assignees) {
@@ -243,66 +244,62 @@ export default {
:title="displayContributorBadgeText"
>{{ __('Contributor') }}</span
>
- <div v-if="canResolve" class="gl-ml-2">
- <gl-button
- ref="resolveButton"
- v-gl-tooltip
- size="small"
- category="tertiary"
- :variant="resolveVariant"
- :class="{ 'is-disabled': !resolvable, 'is-active': isResolved }"
- :title="resolveButtonTitle"
- :aria-label="resolveButtonTitle"
- :icon="resolveIcon"
- :loading="isResolving"
- class="line-resolve-btn note-action-button"
- @click="onResolve"
- />
- </div>
- <div v-if="canAwardEmoji" class="gl-ml-3 gl-mr-2">
- <a
- v-gl-tooltip
- :class="{ 'js-user-authored': isAuthoredByCurrentUser }"
- class="note-action-button note-emoji-button js-add-award js-note-emoji"
- href="#"
- title="Add reaction"
- data-position="right"
- >
- <gl-icon class="link-highlight award-control-icon-neutral" name="slight-smile" />
- <gl-icon class="link-highlight award-control-icon-positive" name="smiley" />
- <gl-icon class="link-highlight award-control-icon-super-positive" name="smile" />
- </a>
- </div>
+ <gl-button
+ v-if="canResolve"
+ ref="resolveButton"
+ v-gl-tooltip
+ size="small"
+ category="tertiary"
+ :variant="resolveVariant"
+ :class="{ 'is-disabled': !resolvable, 'is-active': isResolved }"
+ :title="resolveButtonTitle"
+ :aria-label="resolveButtonTitle"
+ :icon="resolveIcon"
+ :loading="isResolving"
+ class="line-resolve-btn note-action-button"
+ @click="onResolve"
+ />
+ <a
+ v-if="canAwardEmoji"
+ v-gl-tooltip
+ :class="{ 'js-user-authored': isAuthoredByCurrentUser }"
+ class="note-action-button note-emoji-button js-add-award js-note-emoji gl-text-gray-600 gl-m-2"
+ href="#"
+ title="Add reaction"
+ data-position="right"
+ >
+ <gl-icon class="link-highlight award-control-icon-neutral" name="slight-smile" />
+ <gl-icon class="link-highlight award-control-icon-positive" name="smiley" />
+ <gl-icon class="link-highlight award-control-icon-super-positive" name="smile" />
+ </a>
<reply-button
v-if="showReply"
ref="replyButton"
class="js-reply-button"
@startReplying="$emit('startReplying')"
/>
- <div v-if="canEdit" class="gl-ml-2">
- <gl-button
- v-gl-tooltip
- title="Edit comment"
- icon="pencil"
- size="small"
- category="tertiary"
- class="note-action-button js-note-edit btn btn-transparent"
- data-qa-selector="note_edit_button"
- @click="onEdit"
- />
- </div>
- <div v-if="showDeleteAction" class="gl-ml-2">
- <gl-button
- v-gl-tooltip
- title="Delete comment"
- size="small"
- icon="remove"
- category="tertiary"
- class="note-action-button js-note-delete btn btn-transparent"
- @click="onDelete"
- />
- </div>
- <div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions gl-ml-2">
+ <gl-button
+ v-if="canEdit"
+ v-gl-tooltip
+ title="Edit comment"
+ icon="pencil"
+ size="small"
+ category="tertiary"
+ class="note-action-button js-note-edit btn btn-transparent"
+ data-qa-selector="note_edit_button"
+ @click="onEdit"
+ />
+ <gl-button
+ v-if="showDeleteAction"
+ v-gl-tooltip
+ title="Delete comment"
+ size="small"
+ icon="remove"
+ category="tertiary"
+ class="note-action-button js-note-delete btn btn-transparent"
+ @click="onDelete"
+ />
+ <div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions">
<gl-button
v-gl-tooltip
title="More actions"
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 acbbee13a6d..5ce03091504 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -1,7 +1,11 @@
<script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
export default {
+ i18n: {
+ buttonText: __('Reply to comment'),
+ },
name: 'ReplyButton',
components: {
GlButton,
@@ -13,18 +17,15 @@ export default {
</script>
<template>
- <div class="gl-ml-2">
- <gl-button
- ref="button"
- v-gl-tooltip
- data-track-event="click_button"
- data-track-label="reply_comment_button"
- category="tertiary"
- size="small"
- icon="comment"
- :title="__('Reply to comment')"
- :aria-label="__('Reply to comment')"
- @click="$emit('startReplying')"
- />
- </div>
+ <gl-button
+ v-gl-tooltip
+ data-track-event="click_button"
+ data-track-label="reply_comment_button"
+ category="tertiary"
+ size="small"
+ icon="comment"
+ :title="$options.i18n.buttonText"
+ :aria-label="$options.i18n.buttonText"
+ @click="$emit('startReplying')"
+ />
</template>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index cf3e991986c..9eb7b928ea4 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -1,8 +1,8 @@
<script>
import { mapActions, mapGetters } from 'vuex';
+import { __ } from '~/locale';
import AwardsList from '~/vue_shared/components/awards_list.vue';
import { deprecatedCreateFlash as Flash } from '../../flash';
-import { __ } from '~/locale';
export default {
components: {
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index 8855ceac3d5..8c5d81c0cc9 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,14 +1,16 @@
<script>
/* eslint-disable vue/no-v-html */
-import { mapActions, mapGetters, mapState } from 'vuex';
import $ from 'jquery';
+import { escape } from 'lodash';
+import { mapActions, mapGetters, mapState } from 'vuex';
+
import '~/behaviors/markdown/render_gfm';
-import noteEditedText from './note_edited_text.vue';
-import noteAwardsList from './note_awards_list.vue';
+import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
+import autosave from '../mixins/autosave';
import noteAttachment from './note_attachment.vue';
+import noteAwardsList from './note_awards_list.vue';
+import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
-import autosave from '../mixins/autosave';
-import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
export default {
components: {
@@ -29,6 +31,11 @@ export default {
required: false,
default: null,
},
+ file: {
+ type: Object,
+ required: false,
+ default: null,
+ },
canEdit: {
type: Boolean,
required: true,
@@ -46,6 +53,7 @@ export default {
},
computed: {
...mapGetters(['getDiscussion', 'suggestionsCount']),
+ ...mapGetters('diffs', ['suggestionCommitMessage']),
discussion() {
if (!this.note.isDraft) return {};
@@ -54,7 +62,6 @@ export default {
...mapState({
batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo,
}),
- ...mapState('diffs', ['defaultSuggestionCommitMessage']),
noteBody() {
return this.note.note;
},
@@ -64,6 +71,21 @@ export default {
lineType() {
return this.line ? this.line.type : null;
},
+ commitMessage() {
+ // Please see this issue comment for why these
+ // are hard-coded to 1:
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/291027#note_468308022
+ const suggestionsCount = 1;
+ const filesCount = 1;
+ const filePaths = this.file ? [this.file.file_path] : [];
+ const suggestion = this.suggestionCommitMessage({
+ file_paths: filePaths.join(', '),
+ suggestions_count: suggestionsCount,
+ files_count: filesCount,
+ });
+
+ return escape(suggestion);
+ },
},
mounted() {
this.renderGFM();
@@ -135,7 +157,7 @@ export default {
:note-html="note.note_html"
:line-type="lineType"
:help-page-path="helpPagePath"
- :default-commit-message="defaultSuggestionCommitMessage"
+ :default-commit-message="commitMessage"
@apply="applySuggestion"
@applyBatch="applySuggestionBatch"
@addToBatch="addSuggestionToBatch"
@@ -156,6 +178,7 @@ export default {
@handleFormUpdate="handleFormUpdate"
@cancelForm="formCancelHandler"
/>
+ <!-- eslint-disable vue/no-mutating-props -->
<textarea
v-if="canEdit"
v-model="note.note"
@@ -163,6 +186,7 @@ export default {
class="hidden js-task-list-field"
dir="auto"
></textarea>
+ <!-- eslint-enable vue/no-mutating-props -->
<note-edited-text
v-if="note.last_edited_at"
:edited-at="note.last_edited_at"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 9acb837c27f..653bc450d0b 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,14 +1,15 @@
<script>
/* eslint-disable vue/no-v-html */
+import { GlButton } from '@gitlab/ui';
import { mapGetters, mapActions, mapState } from 'vuex';
+import { getDraft, updateDraft } from '~/lib/utils/autosave';
import { mergeUrlParams } from '~/lib/utils/url_utility';
-import eventHub from '../event_hub';
+import { __, sprintf } from '~/locale';
import markdownField from '~/vue_shared/components/markdown/field.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import eventHub from '../event_hub';
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 {
@@ -16,6 +17,7 @@ export default {
components: {
markdownField,
CommentFieldLayout,
+ GlButton,
},
mixins: [glFeatureFlagsMixin(), issuableStateMixin, resolvable],
props: {
@@ -378,61 +380,70 @@ export default {
</template>
</label>
</p>
- <div>
- <button
+ <div class="gl-display-sm-flex gl-flex-wrap">
+ <gl-button
:disabled="isDisabled"
- type="button"
- class="btn btn-success"
+ category="primary"
+ variant="success"
+ class="gl-mr-3"
data-qa-selector="start_review_button"
@click="handleAddToReview"
>
<template v-if="hasDrafts">{{ __('Add to review') }}</template>
<template v-else>{{ __('Start a review') }}</template>
- </button>
- <button
+ </gl-button>
+ <gl-button
:disabled="isDisabled"
- type="button"
- class="btn js-comment-button"
+ category="secondary"
+ variant="default"
data-qa-selector="comment_now_button"
+ class="gl-mr-3 js-comment-button"
@click="handleUpdate()"
>
{{ __('Add comment now') }}
- </button>
- <button
- class="btn note-edit-cancel js-close-discussion-note-form"
- type="button"
+ </gl-button>
+ <gl-button
+ class="note-edit-cancel js-close-discussion-note-form"
+ category="secondary"
+ variant="default"
data-testid="cancelBatchCommentsEnabled"
@click="cancelHandler(true)"
>
{{ __('Cancel') }}
- </button>
+ </gl-button>
</div>
</template>
<template v-else>
- <button
- :disabled="isDisabled"
- type="button"
- class="js-vue-issue-save btn btn-success js-comment-button"
- data-qa-selector="reply_comment_button"
- @click="handleUpdate()"
- >
- {{ saveButtonTitle }}
- </button>
- <button
- v-if="discussion.resolvable"
- class="btn btn-default gl-mr-3 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"
- data-testid="cancel"
- @click="cancelHandler(true)"
- >
- {{ __('Cancel') }}
- </button>
+ <div class="gl-display-sm-flex gl-flex-wrap">
+ <gl-button
+ :disabled="isDisabled"
+ category="primary"
+ variant="success"
+ data-qa-selector="reply_comment_button"
+ class="gl-mr-3 js-vue-issue-save js-comment-button"
+ @click="handleUpdate()"
+ >
+ {{ saveButtonTitle }}
+ </gl-button>
+ <gl-button
+ v-if="discussion.resolvable"
+ category="secondary"
+ variant="default"
+ class="gl-mr-3 js-comment-resolve-button"
+ @click.prevent="handleUpdate(true)"
+ >
+ {{ resolveButtonTitle }}
+ </gl-button>
+ <gl-button
+ class="note-edit-cancel js-close-discussion-note-form"
+ category="secondary"
+ variant="default"
+ data-testid="cancel"
+ @click="cancelHandler(true)"
+ >
+ {{ __('Cancel') }}
+ </gl-button>
+ </div>
</template>
</div>
</form>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 17a995018d3..6932af61c69 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,9 +1,9 @@
<script>
/* eslint-disable vue/no-v-html */
+import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { mapActions } from 'vuex';
-import { GlIcon, GlLoadingIcon, GlTooltipDirective, GlSprintf } from '@gitlab/ui';
-import { isUserBusy } from '~/set_status_modal/utils';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserNameWithStatus from '../../sidebar/components/assignees/user_name_with_status.vue';
export default {
components: {
@@ -12,7 +12,7 @@ export default {
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
GlIcon,
GlLoadingIcon,
- GlSprintf,
+ UserNameWithStatus,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -90,10 +90,6 @@ export default {
}
return false;
},
- authorIsBusy() {
- const { status } = this.author;
- return status?.availability && isUserBusy(status.availability);
- },
emojiElement() {
return this.$refs?.authorStatus?.querySelector('gl-emoji');
},
@@ -133,6 +129,9 @@ export default {
this.$refs.authorNameLink.dispatchEvent(new Event('mouseleave'));
this.isUsernameLinkHovered = false;
},
+ userAvailability(selectedAuthor) {
+ return selectedAuthor?.availability || '';
+ },
},
};
</script>
@@ -158,12 +157,11 @@ export default {
:data-username="author.username"
>
<slot name="note-header-info"></slot>
- <span class="note-header-author-name gl-font-weight-bold">
- <gl-sprintf v-if="authorIsBusy" :message="s__('UserAvailability|%{author} (Busy)')">
- <template #author>{{ authorName }}</template>
- </gl-sprintf>
- <template v-else>{{ authorName }}</template>
- </span>
+ <user-name-with-status
+ :name="authorName"
+ :availability="userAvailability(author)"
+ container-classes="note-header-author-name gl-font-weight-bold"
+ />
</a>
<span
v-if="authorStatus"
@@ -210,9 +208,9 @@ export default {
v-gl-tooltip:tooltipcontainer.bottom
data-testid="confidentialIndicator"
name="eye-slash"
- :size="14"
- :title="s__('Notes|Private comments are accessible by internal staff only')"
- class="gl-ml-1 gl-text-gray-700 align-middle"
+ :size="16"
+ :title="s__('Notes|This comment is confidential and only visible to project members')"
+ class="gl-ml-1 gl-text-orange-700 align-middle"
/>
<slot name="extra-controls"></slot>
<gl-loading-icon
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 0a9a3da6069..34dd21dcbac 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,22 +1,22 @@
<script>
-import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
-import { s__, __ } from '~/locale';
+import { mapActions, mapGetters } from 'vuex';
+import DraftNote from '~/batch_comments/components/draft_note.vue';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
+import { s__, __ } from '~/locale';
+import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-import DraftNote from '~/batch_comments/components/draft_note.vue';
import { deprecatedCreateFlash as Flash } from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import diffDiscussionHeader from './diff_discussion_header.vue';
-import noteSignedOutWidget from './note_signed_out_widget.vue';
-import noteForm from './note_form.vue';
-import diffWithNote from './diff_with_note.vue';
+import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
-import eventHub from '../event_hub';
-import DiscussionNotes from './discussion_notes.vue';
+import diffDiscussionHeader from './diff_discussion_header.vue';
+import diffWithNote from './diff_with_note.vue';
import DiscussionActions from './discussion_actions.vue';
+import DiscussionNotes from './discussion_notes.vue';
+import noteForm from './note_form.vue';
+import noteSignedOutWidget from './note_signed_out_widget.vue';
export default {
name: 'NoteableDiscussion',
@@ -265,16 +265,8 @@ export default {
<div
v-else-if="showReplies"
:class="{ 'is-replying': isReplying }"
- class="discussion-reply-holder clearfix"
+ class="discussion-reply-holder gl-border-t-0! clearfix"
>
- <user-avatar-link
- v-if="!isReplying && userCanReply"
- :link-href="currentUser.path"
- :img-src="currentUser.avatar_url"
- :img-alt="currentUser.name"
- :img-size="40"
- class="d-none d-sm-block"
- />
<discussion-actions
v-if="!isReplying && userCanReply"
:discussion="discussion"
@@ -285,27 +277,18 @@ export default {
@showReplyForm="showReplyForm"
@resolve="resolveHandler"
/>
- <div v-if="isReplying" class="avatar-note-form-holder">
- <user-avatar-link
- v-if="currentUser"
- :link-href="currentUser.path"
- :img-src="currentUser.avatar_url"
- :img-alt="currentUser.name"
- :img-size="40"
- class="d-none d-sm-block"
- />
- <note-form
- ref="noteForm"
- :discussion="discussion"
- :is-editing="false"
- :line="diffLine"
- save-button-title="Comment"
- :autosave-key="autosaveKey"
- @handleFormUpdateAddToReview="addReplyToReview"
- @handleFormUpdate="saveReply"
- @cancelForm="cancelReplyForm"
- />
- </div>
+ <note-form
+ v-if="isReplying"
+ ref="noteForm"
+ :discussion="discussion"
+ :is-editing="false"
+ :line="diffLine"
+ save-button-title="Comment"
+ :autosave-key="autosaveKey"
+ @handleFormUpdateAddToReview="addReplyToReview"
+ @handleFormUpdate="saveReply"
+ @cancelForm="cancelReplyForm"
+ />
<note-signed-out-widget v-if="!isLoggedIn" />
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index eaa64cf7c01..4343fac3cfa 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,21 +1,18 @@
<script>
+import { GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import $ from 'jquery';
-import { mapGetters, mapActions } from 'vuex';
import { escape } from 'lodash';
-import { GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { mapGetters, mapActions } from 'vuex';
+import { INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
+import httpStatusCodes from '~/lib/utils/http_status';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-import { __, s__, sprintf } from '../../locale';
import { deprecatedCreateFlash as Flash } from '../../flash';
+import { __, s__, sprintf } from '../../locale';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import noteHeader from './note_header.vue';
-import noteActions from './note_actions.vue';
-import NoteBody from './note_body.vue';
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
-import httpStatusCodes from '~/lib/utils/http_status';
import {
getStartLineNumber,
getEndLineNumber,
@@ -23,7 +20,9 @@ import {
commentLineOptions,
formatLineRange,
} from './multiline_comment_utils';
-import { INLINE_DIFF_LINES_KEY } from '~/diffs/constants';
+import noteActions from './note_actions.vue';
+import NoteBody from './note_body.vue';
+import noteHeader from './note_header.vue';
export default {
name: 'NoteableNote',
@@ -38,7 +37,7 @@ export default {
directives: {
SafeHtml,
},
- mixins: [noteable, resolvable, glFeatureFlagsMixin()],
+ mixins: [noteable, resolvable],
props: {
note: {
type: Object,
@@ -160,7 +159,6 @@ export default {
},
showMultiLineComment() {
if (
- !this.glFeatures.multilineComments ||
!this.discussionRoot ||
this.startLineNumber.length === 0 ||
this.endLineNumber.length === 0
@@ -289,6 +287,7 @@ export default {
};
this.isRequesting = true;
this.oldContent = this.note.note_html;
+ // eslint-disable-next-line vue/no-mutating-props
this.note.note_html = escape(noteText);
this.updateNote(data)
@@ -321,6 +320,7 @@ export default {
}
this.$refs.noteBody.resetAutoSave();
if (this.oldContent) {
+ // eslint-disable-next-line vue/no-mutating-props
this.note.note_html = this.oldContent;
this.oldContent = null;
}
@@ -330,6 +330,7 @@ export default {
recoverNoteContent(noteText) {
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
+ // eslint-disable-next-line vue/no-mutating-props
this.note.note = noteText;
const { noteBody } = this.$refs;
if (noteBody) {
@@ -428,6 +429,7 @@ export default {
ref="noteBody"
:note="note"
:line="line"
+ :file="diffFile"
:can-edit="note.current_user.can_edit"
:is-editing="isEditing"
:help-page-path="helpPagePath"
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index e9e687a8743..2d66e0d24e3 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -1,21 +1,22 @@
<script>
import { mapGetters, mapActions } from 'vuex';
-import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
+import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
+import { __ } from '~/locale';
+import initUserPopovers from '~/user_popovers';
+import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { deprecatedCreateFlash as Flash } from '../../flash';
-import * as constants from '../constants';
-import eventHub from '../event_hub';
-import noteableNote from './noteable_note.vue';
-import noteableDiscussion from './noteable_discussion.vue';
-import discussionFilterNote from './discussion_filter_note.vue';
-import systemNote from '../../vue_shared/components/notes/system_note.vue';
-import commentForm from './comment_form.vue';
+import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
-import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
-import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
-import { __ } from '~/locale';
-import initUserPopovers from '~/user_popovers';
+import systemNote from '../../vue_shared/components/notes/system_note.vue';
+import * as constants from '../constants';
+import eventHub from '../event_hub';
+import commentForm from './comment_form.vue';
+import discussionFilterNote from './discussion_filter_note.vue';
+import noteableDiscussion from './noteable_discussion.vue';
+import noteableNote from './noteable_note.vue';
export default {
name: 'NotesApp',
@@ -30,6 +31,7 @@ export default {
discussionFilterNote,
OrderedLayout,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
noteableData: {
type: Object,
@@ -57,7 +59,6 @@ export default {
},
data() {
return {
- isFetching: false,
currentFilter: null,
};
},
@@ -68,6 +69,7 @@ export default {
'convertedDisscussionIds',
'getNotesDataByProp',
'isLoading',
+ 'isFetching',
'commentsDisabled',
'getNoteableData',
'userCanReply',
@@ -103,6 +105,13 @@ export default {
},
},
watch: {
+ async isFetching() {
+ if (!this.isFetching) {
+ await this.$nextTick();
+ await this.startTaskList();
+ await this.checkLocationHash();
+ }
+ },
shouldShow() {
if (!this.isNotesFetched) {
this.fetchNotes();
@@ -153,6 +162,7 @@ export default {
},
methods: {
...mapActions([
+ 'setFetchingState',
'setLoadingState',
'fetchDiscussions',
'poll',
@@ -183,7 +193,11 @@ export default {
fetchNotes() {
if (this.isFetching) return null;
- this.isFetching = true;
+ this.setFetchingState(true);
+
+ if (this.glFeatures.paginatedNotes) {
+ return this.initPolling();
+ }
return this.fetchDiscussions(this.getFetchDiscussionsConfig())
.then(this.initPolling)
@@ -191,11 +205,8 @@ export default {
this.setLoadingState(false);
this.setNotesFetchedState(true);
eventHub.$emit('fetchedNotesData');
- this.isFetching = false;
+ this.setFetchingState(false);
})
- .then(this.$nextTick)
- .then(this.startTaskList)
- .then(this.checkLocationHash)
.catch(() => {
this.setLoadingState(false);
this.setNotesFetchedState(true);
diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue
index c279a7107c7..ed1f456c174 100644
--- a/app/assets/javascripts/notes/components/sort_discussion.vue
+++ b/app/assets/javascripts/notes/components/sort_discussion.vue
@@ -2,8 +2,8 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { __ } from '~/locale';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import Tracking from '~/tracking';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { ASC, DESC } from '../constants';
const SORT_OPTIONS = [
diff --git a/app/assets/javascripts/notes/components/timeline_toggle.vue b/app/assets/javascripts/notes/components/timeline_toggle.vue
index 8162878f80d..87d22e5b986 100644
--- a/app/assets/javascripts/notes/components/timeline_toggle.vue
+++ b/app/assets/javascripts/notes/components/timeline_toggle.vue
@@ -2,9 +2,9 @@
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { s__ } from '~/locale';
+import TrackEventDirective from '~/vue_shared/directives/track_event';
import { COMMENTS_ONLY_FILTER_VALUE, DESC } from '../constants';
import notesEventHub from '../event_hub';
-import TrackEventDirective from '~/vue_shared/directives/track_event';
import { trackToggleTimelineView } from '../utils';
export const timelineEnabledTooltip = s__('Timeline|Turn timeline view off');
diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
index ab7fa793bdc..01e3f84d00e 100644
--- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue
+++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
@@ -1,8 +1,8 @@
<script>
-import { uniqBy } from 'lodash';
import { GlButton, GlIcon } from '@gitlab/ui';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import { uniqBy } from 'lodash';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
export default {
components: {
@@ -39,13 +39,17 @@ export default {
this.$emit('toggle');
},
},
+ ICON_CLASS: 'gl-mr-3 gl-cursor-pointer',
};
</script>
<template>
- <li :class="className" class="replies-toggle js-toggle-replies">
+ <li
+ :class="className"
+ class="replies-toggle js-toggle-replies gl-display-flex! gl-align-items-center gl-flex-wrap"
+ >
<template v-if="collapsed">
- <gl-icon name="chevron-right" @click.native="toggle" />
+ <gl-icon :class="$options.ICON_CLASS" name="chevron-right" @click.native="toggle" />
<div>
<user-avatar-link
v-for="author in uniqueAuthors"
@@ -59,7 +63,7 @@ export default {
/>
</div>
<gl-button
- class="js-replies-text"
+ class="js-replies-text gl-mr-2"
category="tertiary"
variant="link"
data-qa-selector="expand_replies_button"
@@ -68,18 +72,19 @@ export default {
{{ replies.length }} {{ n__('reply', 'replies', replies.length) }}
</gl-button>
{{ __('Last reply by') }}
- <a :href="lastReply.author.path" class="btn btn-link author-link">
+ <a :href="lastReply.author.path" class="btn btn-link author-link gl-mx-2">
{{ lastReply.author.name }}
</a>
<time-ago-tooltip :time="lastReply.created_at" tooltip-placement="bottom" />
</template>
- <span
+ <div
v-else
- class="collapse-replies-btn js-collapse-replies"
+ class="collapse-replies-btn js-collapse-replies gl-display-flex align-items-center"
data-qa-selector="collapse_replies_button"
@click="toggle"
>
- <gl-icon name="chevron-down" /> {{ s__('Notes|Collapse replies') }}
- </span>
+ <gl-icon :class="$options.ICON_CLASS" name="chevron-down" />
+ <span class="gl-cursor-pointer">{{ s__('Notes|Collapse replies') }}</span>
+ </div>
</li>
</template>
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 1f0b2afab9e..e4241669fbc 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -2,8 +2,8 @@ import Vue from 'vue';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
import initSortDiscussions from './sort_discussions';
-import initTimelineToggle from './timeline';
import { store } from './stores';
+import initTimelineToggle from './timeline';
const el = document.getElementById('js-vue-notes');
diff --git a/app/assets/javascripts/notes/mixins/autosave.js b/app/assets/javascripts/notes/mixins/autosave.js
index b161773f5f1..d670d0bd4c5 100644
--- a/app/assets/javascripts/notes/mixins/autosave.js
+++ b/app/assets/javascripts/notes/mixins/autosave.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
+import { s__ } from '~/locale';
import Autosave from '../../autosave';
import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
-import { s__ } from '~/locale';
export default {
methods: {
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 5ce541781d4..76342e07c04 100644
--- a/app/assets/javascripts/notes/mixins/diff_line_note_form.js
+++ b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
@@ -2,8 +2,8 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import { getDraftReplyFormData, getDraftFormData } from '~/batch_comments/utils';
import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { s__ } from '~/locale';
import { clearDraft } from '~/lib/utils/autosave';
+import { s__ } from '~/locale';
import { formatLineRange } from '~/notes/components/multiline_comment_utils';
export default {
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index c6684efed4d..19403c29cda 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -1,23 +1,24 @@
-import Vue from 'vue';
import $ from 'jquery';
import Visibility from 'visibilityjs';
+import Vue from 'vue';
+import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
-import TaskList from '../../task_list';
-import { deprecatedCreateFlash as Flash } from '../../flash';
-import Poll from '../../lib/utils/poll';
-import * as types from './mutation_types';
-import * as utils from './utils';
-import * as constants from '../constants';
+import { __, sprintf } from '~/locale';
+import updateIssueConfidentialMutation from '~/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql';
+import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql';
+import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql';
import loadAwardsHandler from '../../awards_handler';
-import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
+import { deprecatedCreateFlash as Flash } from '../../flash';
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
+import Poll from '../../lib/utils/poll';
import { mergeUrlParams } from '../../lib/utils/url_utility';
+import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
+import TaskList from '../../task_list';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
-import updateIssueConfidentialMutation from '~/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql';
-import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql';
-import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql';
-import { __, sprintf } from '~/locale';
-import Api from '~/api';
+import * as constants from '../constants';
+import eventHub from '../event_hub';
+import * as types from './mutation_types';
+import * as utils from './utils';
let eTagPoll;
@@ -420,14 +421,25 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
.catch(processErrors);
};
-const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
+export const setFetchingState = ({ commit }, fetchingState) =>
+ commit(types.SET_NOTES_FETCHING_STATE, fetchingState);
+
+const pollSuccessCallBack = async (resp, commit, state, getters, dispatch) => {
if (state.isResolvingDiscussion) {
return null;
}
+ if (window.gon?.features?.paginatedNotes && !resp.more && state.isFetching) {
+ eventHub.$emit('fetchedNotesData');
+ dispatch('setFetchingState', false);
+ dispatch('setNotesFetchedState', true);
+ dispatch('setLoadingState', false);
+ }
+
if (resp.notes?.length) {
- dispatch('updateOrCreateNotes', resp.notes);
+ await dispatch('updateOrCreateNotes', resp.notes);
dispatch('startTaskList');
+ dispatch('updateResolvableDiscussionsCounts');
}
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
@@ -727,9 +739,13 @@ export const updateConfidentialityOnIssuable = (
})
.then(({ data }) => {
const {
- issueSetConfidential: { issue },
+ issueSetConfidential: { issue, errors },
} = data;
- setConfidentiality({ commit }, issue.confidential);
+ if (errors?.length) {
+ Flash(errors[0], 'alert');
+ } else {
+ setConfidentiality({ commit }, issue.confidential);
+ }
});
};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 5891a2e63e3..43d99937b8d 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -48,6 +48,8 @@ export const persistSortOrder = (state) => state.persistSortOrder;
export const timelineEnabled = (state) => state.isTimelineEnabled;
+export const isFetching = (state) => state.isFetching;
+
export const isLoading = (state) => state.isLoading;
export const getNotesDataByProp = (state) => (prop) => state.notesData[prop];
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 144a3d7ba90..f154edd3434 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -1,7 +1,7 @@
+import { ASC } from '../../constants';
import * as actions from '../actions';
import * as getters from '../getters';
import mutations from '../mutations';
-import { ASC } from '../../constants';
export default () => ({
state: {
@@ -47,6 +47,7 @@ export default () => ({
unresolvedDiscussionsCount: 0,
descriptionVersions: {},
isTimelineEnabled: false,
+ isFetching: false,
},
actions,
getters,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 5c4f62f4575..2e8b728e013 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE';
export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
export const UPDATE_DISCUSSION_POSITION = 'UPDATE_DISCUSSION_POSITION';
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
+export const SET_NOTES_FETCHING_STATE = 'SET_NOTES_FETCHING_STATE';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 2c51ce0d970..c5fa34dfedd 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -1,7 +1,8 @@
-import * as utils from './utils';
-import * as types from './mutation_types';
-import * as constants from '../constants';
+import { isEqual } from 'lodash';
import { isInMRPage } from '../../lib/utils/common_utils';
+import * as constants from '../constants';
+import * as types from './mutation_types';
+import * as utils from './utils';
export default {
[types.ADD_NEW_NOTE](state, data) {
@@ -31,7 +32,22 @@ export default {
}
}
- note.base_discussion = undefined; // No point keeping a reference to this
+ if (window.gon?.features?.paginatedNotes && note.base_discussion) {
+ if (discussion.diff_file) {
+ discussion.file_hash = discussion.diff_file.file_hash;
+
+ discussion.truncated_diff_lines = utils.prepareDiffLines(
+ discussion.truncated_diff_lines || [],
+ );
+ }
+
+ discussion.resolvable = note.resolvable;
+ discussion.expanded = note.base_discussion.expanded;
+ discussion.resolved = note.resolved;
+ }
+
+ // note.base_discussion = undefined; // No point keeping a reference to this
+ delete note.base_discussion;
discussion.notes = [note];
state.discussions.push(discussion);
@@ -220,6 +236,11 @@ export default {
[types.UPDATE_NOTE](state, note) {
const noteObj = utils.findNoteObjectById(state.discussions, note.discussion_id);
+ // Disable eslint here so we can delete the property that we no longer need
+ // in the note object
+ // eslint-disable-next-line no-param-reassign
+ delete note.base_discussion;
+
if (noteObj.individual_note) {
if (note.type === constants.DISCUSSION_NOTE) {
noteObj.individual_note = false;
@@ -228,7 +249,10 @@ export default {
noteObj.notes.splice(0, 1, note);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
- noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
+
+ if (!isEqual(comment, note)) {
+ noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
+ }
}
},
@@ -313,6 +337,10 @@ export default {
state.isLoading = value;
},
+ [types.SET_NOTES_FETCHING_STATE](state, value) {
+ state.isFetching = value;
+ },
+
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index 6df926e1249..627e405c75c 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -1,7 +1,7 @@
-import AjaxCache from '~/lib/utils/ajax_cache';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
-import { sprintf, __ } from '~/locale';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import { sprintf, __ } from '~/locale';
// factory function because global flag makes RegExp stateful
const createQuickActionsRegex = () => /^\/\w+.*$/gm;