diff options
Diffstat (limited to 'app/assets/javascripts/notes/components')
13 files changed, 148 insertions, 106 deletions
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index cfdadbceaf6..9cc53a320b8 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -3,7 +3,7 @@ import $ from 'jquery'; import { mapActions, mapGetters, mapState } from 'vuex'; import { isEmpty } from 'lodash'; import Autosize from 'autosize'; -import { GlAlert, GlIntersperse, GlLink, GlSprintf, GlButton } from '@gitlab/ui'; +import { GlAlert, GlIntersperse, GlLink, GlSprintf, 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'; @@ -38,6 +38,7 @@ export default { GlIntersperse, GlLink, GlSprintf, + GlIcon, }, mixins: [issuableStateMixin], props: { @@ -342,7 +343,7 @@ 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-sm-none d-md-block"> + <div class="timeline-icon d-none d-md-block"> <user-avatar-link v-if="author" :link-href="author.path" @@ -457,7 +458,7 @@ export default { class="btn btn-transparent" @click.prevent="setNoteType('comment')" > - <i aria-hidden="true" class="fa fa-check icon"></i> + <gl-icon name="check" class="icon" /> <div class="description"> <strong>{{ __('Comment') }}</strong> <p> @@ -476,7 +477,7 @@ export default { data-qa-selector="discussion_menu_item" @click.prevent="setNoteType('discussion')" > - <i aria-hidden="true" class="fa fa-check icon"></i> + <gl-icon name="check" class="icon" /> <div class="description"> <strong>{{ __('Start thread') }}</strong> <p>{{ startDiscussionDescription }}</p> diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index a4271852563..91cf682943e 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -7,6 +7,7 @@ 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 { isCollapsed } from '../../diffs/diff_file'; const FIRST_CHAR_REGEX = /^(\+|-| )/; @@ -46,6 +47,9 @@ export default { this.discussion.truncated_diff_lines && this.discussion.truncated_diff_lines.length !== 0 ); }, + isCollapsed() { + return isCollapsed(this.discussion.diff_file); + }, }, mounted() { if (this.isTextFile && !this.hasTruncatedDiffLines) { @@ -76,7 +80,7 @@ export default { :discussion-path="discussion.discussion_path" :diff-file="discussion.diff_file" :can-current-user-fork="false" - :expanded="!discussion.diff_file.viewer.automaticallyCollapsed" + :expanded="!isCollapsed" /> <div v-if="isTextFile" class="diff-content"> <table class="code js-syntax-highlight" :class="$options.userColorSchemeClass"> diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue index 878a748e99a..0272790a75d 100644 --- a/app/assets/javascripts/notes/components/discussion_actions.vue +++ b/app/assets/javascripts/notes/components/discussion_actions.vue @@ -45,7 +45,7 @@ export default { return this.discussion.notes.filter(x => x.resolvable); }, userCanResolveDiscussion() { - return this.resolvableNotes.every(note => note.current_user && note.current_user.can_resolve); + return this.resolvableNotes.every(note => note.current_user?.can_resolve_discussion); }, }, }; diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index e4b191b55a7..08c22f0b4c6 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -116,7 +116,8 @@ export default { <gl-dropdown v-if="displayFilters" id="discussion-filter-dropdown" - class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container qa-discussion-filter" + class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container" + data-qa-selector="discussion_filter_dropdown" :text="currentFilter.title" > <div v-for="filter in filters" :key="filter.value" class="dropdown-item-wrapper"> @@ -125,7 +126,7 @@ export default { :is-checked="filter.value === currentValue" :class="{ 'is-active': filter.value === currentValue }" :data-filter-type="filterType(filter.value)" - class="qa-filter-options" + data-qa-selector="filter_menu_item" @click.prevent="selectFilter(filter.value)" > {{ filter.title }} diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue index ae6646cf96c..83326279423 100644 --- a/app/assets/javascripts/notes/components/discussion_filter_note.vue +++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue @@ -1,28 +1,19 @@ <script> -/* eslint-disable vue/no-v-html */ -import { GlButton, GlIcon } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; +import { GlButton, GlIcon, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; import notesEventHub from '../event_hub'; export default { + i18n: { + information: s__( + "Notes|You're only seeing %{boldStart}other activity%{boldEnd} in the feed. To add a comment, switch to one of the following options.", + ), + }, components: { GlButton, GlIcon, - }, - computed: { - timelineContent() { - return sprintf( - __( - "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options.", - ), - { - startTag: `<b>`, - endTag: `</b>`, - }, - false, - ); - }, + GlSprintf, }, methods: { selectFilter(value) { @@ -33,17 +24,26 @@ export default { </script> <template> - <li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note"> + <li + class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note" + data-qa-selector="discussion_filter_container" + > <div class="timeline-icon d-none d-lg-flex"> <gl-icon name="comment" /> </div> <div class="timeline-content"> - <div ref="timelineContent" v-html="timelineContent"></div> + <div data-testid="discussion-filter-timeline-content"> + <gl-sprintf :message="$options.i18n.information"> + <template #bold="{ content }"> + <b>{{ content }}</b> + </template> + </gl-sprintf> + </div> <div class="discussion-filter-actions mt-2"> - <gl-button ref="showAllActivity" variant="default" @click="selectFilter(0)"> + <gl-button variant="default" @click="selectFilter(0)"> {{ __('Show all activity') }} </gl-button> - <gl-button ref="showComments" variant="default" @click="selectFilter(1)"> + <gl-button variant="default" @click="selectFilter(1)"> {{ __('Show comments only') }} </gl-button> </div> diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue index a1e887c47d0..8ac915c3c03 100644 --- a/app/assets/javascripts/notes/components/discussion_notes.vue +++ b/app/assets/javascripts/notes/components/discussion_notes.vue @@ -127,6 +127,7 @@ export default { :help-page-path="helpPagePath" :show-reply-button="userCanReply" :discussion-root="true" + :discussion-resolve-path="discussion.resolve_path" @handleDeleteNote="$emit('deleteNote')" @startReplying="$emit('startReplying')" > @@ -171,6 +172,7 @@ export default { :help-page-path="helpPagePath" :line="diffLine" :discussion-root="index === 0" + :discussion-resolve-path="discussion.resolve_path" @handleDeleteNote="$emit('deleteNote')" > <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot> diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index c2f40b2d21a..fc131f548b4 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -1,6 +1,6 @@ <script> import { mapGetters } from 'vuex'; -import { GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui'; +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'; @@ -14,7 +14,8 @@ export default { components: { GlIcon, ReplyButton, - GlLoadingIcon, + GlButton, + GlDropdownItem, }, directives: { GlTooltip: GlTooltipDirective, @@ -170,6 +171,15 @@ export default { name: this.projectName, }); }, + resolveIcon() { + if (!this.isResolving) { + return this.isResolved ? 'check-circle-filled' : 'check-circle'; + } + return null; + }, + resolveVariant() { + return this.isResolved ? 'success' : 'default'; + }, }, methods: { onEdit() { @@ -233,24 +243,23 @@ export default { :title="displayContributorBadgeText" >{{ __('Contributor') }}</span > - <div v-if="canResolve" class="note-actions-item"> - <button + <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" - type="button" + :icon="resolveIcon" + :loading="isResolving" class="line-resolve-btn note-action-button" @click="onResolve" - > - <template v-if="!isResolving"> - <gl-icon :name="isResolved ? 'check-circle-filled' : 'check-circle'" /> - </template> - <gl-loading-icon v-else inline /> - </button> + /> </div> - <div v-if="canAwardEmoji" class="note-actions-item"> + <div v-if="canAwardEmoji" class="gl-ml-3 gl-mr-2"> <a v-gl-tooltip :class="{ 'js-user-authored': isAuthoredByCurrentUser }" @@ -261,7 +270,7 @@ export default { > <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="smiley" /> + <gl-icon class="link-highlight award-control-icon-super-positive" name="smile" /> </a> </div> <reply-button @@ -270,72 +279,57 @@ export default { class="js-reply-button" @startReplying="$emit('startReplying')" /> - <div v-if="canEdit" class="note-actions-item"> - <button + <div v-if="canEdit" class="gl-ml-2"> + <gl-button v-gl-tooltip - type="button" 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-icon name="pencil" class="link-highlight" /> - </button> + /> </div> - <div v-if="showDeleteAction" class="note-actions-item"> - <button + <div v-if="showDeleteAction" class="gl-ml-2"> + <gl-button v-gl-tooltip - type="button" title="Delete comment" + size="small" + icon="remove" + category="tertiary" class="note-action-button js-note-delete btn btn-transparent" @click="onDelete" - > - <gl-icon name="remove" class="link-highlight" /> - </button> + /> </div> - <div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions note-actions-item"> - <button + <div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions gl-ml-2"> + <gl-button v-gl-tooltip - type="button" title="More actions" + icon="ellipsis_v" + size="small" + category="tertiary" class="note-action-button more-actions-toggle btn btn-transparent" data-toggle="dropdown" @click="closeTooltip" - > - <gl-icon class="icon" name="ellipsis_v" /> - </button> + /> <ul class="dropdown-menu more-actions-dropdown dropdown-open-left"> - <li v-if="canReportAsAbuse"> - <a :href="reportAbusePath">{{ __('Report abuse to admin') }}</a> - </li> - <li v-if="noteUrl"> - <button - :data-clipboard-text="noteUrl" - type="button" - class="btn-default btn-transparent js-btn-copy-note-link" - > - {{ __('Copy link') }} - </button> - </li> - <li v-if="canAssign"> - <button - class="btn-default btn-transparent" - data-testid="assign-user" - type="button" - @click="assignUser" - > - {{ displayAssignUserText }} - </button> - </li> - <li v-if="canEdit"> - <button - class="btn btn-transparent js-note-delete js-note-delete" - type="button" - @click.prevent="onDelete" - > - <span class="text-danger">{{ __('Delete comment') }}</span> - </button> - </li> + <gl-dropdown-item v-if="canReportAsAbuse" :href="reportAbusePath"> + {{ __('Report abuse to admin') }} + </gl-dropdown-item> + <gl-dropdown-item + v-if="noteUrl" + class="js-btn-copy-note-link" + :data-clipboard-text="noteUrl" + > + {{ __('Copy link') }} + </gl-dropdown-item> + <gl-dropdown-item v-if="canAssign" data-testid="assign-user" @click="assignUser"> + {{ displayAssignUserText }} + </gl-dropdown-item> + <gl-dropdown-item v-if="canEdit" class="js-note-delete" @click.prevent="onDelete"> + <span class="text-danger">{{ __('Delete comment') }}</span> + </gl-dropdown-item> </ul> </div> </div> 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 f19b7667fb2..acbbee13a6d 100644 --- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue +++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue @@ -13,7 +13,7 @@ export default { </script> <template> - <div class="note-actions-item"> + <div class="gl-ml-2"> <gl-button ref="button" v-gl-tooltip diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue index 72f9a4c7e74..b20facc4032 100644 --- a/app/assets/javascripts/notes/components/note_attachment.vue +++ b/app/assets/javascripts/notes/components/note_attachment.vue @@ -1,6 +1,11 @@ <script> +import { GlIcon } from '@gitlab/ui'; + export default { name: 'NoteAttachment', + components: { + GlIcon, + }, props: { attachment: { type: Object, @@ -29,7 +34,7 @@ export default { target="_blank" rel="noopener noreferrer" > - <i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }} + <gl-icon name="paperclip" /> {{ attachment.filename }} </a> </div> </div> diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 4b3f23e742d..43f17c5d65c 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -121,7 +121,13 @@ export default { return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit; }, showResolveDiscussionToggle() { - return (this.discussion?.id && this.discussion.resolvable) || this.isDraft; + if (!this.discussion?.notes) return false; + + return ( + this.discussion?.notes + .filter(n => n.resolvable) + .some(n => n.current_user?.can_resolve_discussion) || this.isDraft + ); }, noteHash() { if (this.noteId) { diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue index a13a0dbbf30..cacf209ed81 100644 --- a/app/assets/javascripts/notes/components/note_header.vue +++ b/app/assets/javascripts/notes/components/note_header.vue @@ -1,7 +1,8 @@ <script> /* eslint-disable vue/no-v-html */ import { mapActions } from 'vuex'; -import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; +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'; export default { @@ -11,6 +12,7 @@ export default { import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'), GlIcon, GlLoadingIcon, + GlSprintf, }, directives: { GlTooltip: GlTooltipDirective, @@ -65,8 +67,8 @@ export default { }; }, computed: { - toggleChevronClass() { - return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down'; + toggleChevronIconName() { + return this.expanded ? 'chevron-up' : 'chevron-down'; }, noteTimestampLink() { return this.noteId ? `#note_${this.noteId}` : undefined; @@ -85,9 +87,16 @@ export default { authorStatus() { return this.author.status_tooltip_html; }, + authorIsBusy() { + const { status } = this.author; + return status?.availability && isUserBusy(status.availability); + }, emojiElement() { return this.$refs?.authorStatus?.querySelector('gl-emoji'); }, + authorName() { + return this.author.name; + }, }, mounted() { this.emojiTitle = this.emojiElement ? this.emojiElement.getAttribute('title') : ''; @@ -133,7 +142,7 @@ export default { type="button" @click="handleToggle" > - <i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i> + <gl-icon ref="chevronIcon" :name="toggleChevronIconName" aria-hidden="true" /> {{ __('Toggle thread') }} </button> </div> @@ -146,7 +155,12 @@ export default { :data-username="author.username" > <slot name="note-header-info"></slot> - <span class="note-header-author-name bold">{{ author.name }}</span> + <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> </a> <span v-if="authorStatus" @@ -170,7 +184,9 @@ export default { </template> <span v-else>{{ __('A deleted user') }}</span> <span class="note-headline-light note-headline-meta"> - <span class="system-note-message"> <slot></slot> </span> + <span class="system-note-message" data-qa-selector="system_note_content"> + <slot></slot> + </span> <template v-if="createdAt"> <span ref="actionText" class="system-note-separator"> <template v-if="actionText">{{ actionText }}</template> diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 4f45fcb0062..9be53fe60f2 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -73,6 +73,11 @@ export default { required: false, default: false, }, + discussionResolvePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -81,6 +86,7 @@ export default { isRequesting: false, isResolving: false, commentLineStart: {}, + resolveAsThread: this.glFeatures.removeResolveNote, }; }, computed: { @@ -133,6 +139,10 @@ export default { return this.note.isDraft; }, canResolve() { + if (this.glFeatures.removeResolveNote && !this.discussionRoot) return false; + + if (this.glFeatures.removeResolveNote) return this.note.current_user.can_resolve_discussion; + return ( this.note.current_user.can_resolve || (this.note.isDraft && this.note.discussion_id !== null) @@ -345,7 +355,8 @@ export default { :class="classNameBindings" :data-award-url="note.toggle_award_path" :data-note-id="note.id" - class="note note-wrapper qa-noteable-note-item" + class="note note-wrapper" + data-qa-selector="noteable_note_container" > <div v-if="showMultiLineComment" diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index f49fd2c3fa3..0628e1d8647 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -1,11 +1,12 @@ <script> import { uniqBy } from 'lodash'; -import { GlIcon } from '@gitlab/ui'; +import { GlButton, GlIcon } from '@gitlab/ui'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; export default { components: { + GlButton, GlIcon, UserAvatarLink, TimeAgoTooltip, @@ -57,14 +58,15 @@ export default { tooltip-placement="bottom" /> </div> - <button - class="btn btn-link js-replies-text" + <gl-button + class="js-replies-text" + category="tertiary" + variant="link" data-qa-selector="expand_replies_button" - type="button" @click="toggle" > {{ replies.length }} {{ n__('reply', 'replies', replies.length) }} - </button> + </gl-button> {{ __('Last reply by') }} <a :href="lastReply.author.path" class="btn btn-link author-link"> {{ lastReply.author.name }} |