diff options
Diffstat (limited to 'app/assets/javascripts/notes')
12 files changed, 141 insertions, 143 deletions
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 54fcf41ca50..cfdadbceaf6 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -371,6 +371,7 @@ export default { :markdown-docs-path="markdownDocsPath" :quick-actions-docs-path="quickActionsDocsPath" :add-spacing-classes="false" + :textarea-value="note" > <textarea id="note-body" @@ -380,7 +381,8 @@ export default { dir="auto" :disabled="isSubmitting" name="note[note]" - class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area qa-comment-input" + class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area" + data-qa-selector="comment_field" data-supports-quick-actions="true" :aria-label="__('Description')" :placeholder="__('Write a comment or drag your files here…')" @@ -425,7 +427,8 @@ export default { > <gl-button :disabled="isSubmitButtonDisabled" - class="js-comment-button js-comment-submit-button qa-comment-button" + class="js-comment-button js-comment-submit-button" + data-qa-selector="comment_button" type="submit" category="primary" variant="success" @@ -439,7 +442,8 @@ export default { name="button" category="primary" variant="success" - class="note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown" + class="note-type-toggle js-note-new-discussion dropdown-toggle" + data-qa-selector="note_dropdown" data-display="static" data-toggle="dropdown" icon="chevron-down" @@ -468,7 +472,10 @@ export default { </li> <li class="divider droplab-item-ignore"></li> <li :class="{ 'droplab-item-selected': noteType === 'discussion' }"> - <button class="qa-discussion-option" @click.prevent="setNoteType('discussion')"> + <button + data-qa-selector="discussion_menu_item" + @click.prevent="setNoteType('discussion')" + > <i aria-hidden="true" class="fa fa-check icon"></i> <div class="description"> <strong>{{ __('Start thread') }}</strong> diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue index 8e6c01ba63f..ee39a529345 100644 --- a/app/assets/javascripts/notes/components/diff_discussion_header.vue +++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue @@ -1,7 +1,7 @@ <script> -/* eslint-disable vue/no-v-html */ import { mapActions } from 'vuex'; import { escape } from 'lodash'; +import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import { s__, __, sprintf } from '~/locale'; import { truncateSha } from '~/lib/utils/text_utility'; @@ -17,6 +17,9 @@ export default { noteEditedText, noteHeader, }, + directives: { + SafeHtml, + }, props: { discussion: { type: Object, @@ -113,7 +116,7 @@ export default { :expanded="discussion.expanded" @toggleHandler="toggleDiscussionHandler" > - <span v-html="headerText"></span> + <span v-safe-html="headerText"></span> </note-header> <note-edited-text v-if="discussion.resolved" diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index c01cd8f8037..a4271852563 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -76,7 +76,7 @@ export default { :discussion-path="discussion.discussion_path" :diff-file="discussion.diff_file" :can-current-user-fork="false" - :expanded="!discussion.diff_file.viewer.collapsed" + :expanded="!discussion.diff_file.viewer.automaticallyCollapsed" /> <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_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index c6fab271376..2427a3f98ad 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -1,6 +1,7 @@ <script> import { mapGetters, mapActions } from 'vuex'; -import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { GlTooltipDirective, GlIcon, GlButton, GlButtonGroup } from '@gitlab/ui'; +import { __ } from '~/locale'; import discussionNavigation from '../mixins/discussion_navigation'; export default { @@ -9,6 +10,8 @@ export default { }, components: { GlIcon, + GlButton, + GlButtonGroup, }, mixins: [discussionNavigation], computed: { @@ -34,6 +37,12 @@ export default { allExpanded() { return this.toggeableDiscussions.every(discussion => discussion.expanded); }, + lineResolveClass() { + return this.allResolved ? 'line-resolve-btn is-active' : 'line-resolve-text'; + }, + toggleThreadsLabel() { + return this.allExpanded ? __('Collapse all threads') : __('Expand all threads'); + }, }, methods: { ...mapActions(['setExpandDiscussions']), @@ -51,59 +60,49 @@ export default { <div v-if="resolvableDiscussionsCount > 0" ref="discussionCounter" - class="line-resolve-all-container full-width-mobile" + class="line-resolve-all-container full-width-mobile gl-display-flex d-sm-flex" > - <div class="full-width-mobile d-flex d-sm-flex"> - <div class="line-resolve-all"> - <span - :class="{ 'line-resolve-btn is-active': allResolved, 'line-resolve-text': !allResolved }" - > - <template v-if="allResolved"> - <gl-icon name="check-circle-filled" /> - {{ __('All threads resolved') }} - </template> - <template v-else> - {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }} - </template> - </span> - </div> - <div - v-if="resolveAllDiscussionsIssuePath && !allResolved" - class="btn-group btn-group-sm" - role="group" - > - <a - v-gl-tooltip - :href="resolveAllDiscussionsIssuePath" - :title="s__('Resolve all threads in new issue')" - class="new-issue-for-discussion btn btn-default discussion-create-issue-btn" - > - <gl-icon name="issue-new" /> - </a> - </div> - <div v-if="isLoggedIn && !allResolved" class="btn-group btn-group-sm" role="group"> - <button - v-gl-tooltip - :title="__('Jump to next unresolved thread')" - class="btn btn-default discussion-next-btn" - data-track-event="click_button" - data-track-label="mr_next_unresolved_thread" - data-track-property="click_next_unresolved_thread_top" - @click="jumpToNextDiscussion" - > - <gl-icon name="comment-next" /> - </button> - </div> - <div class="btn-group btn-group-sm" role="group"> - <button - v-gl-tooltip - :title="__('Toggle all threads')" - class="btn btn-default toggle-all-discussions-btn" - @click="handleExpandDiscussions" - > - <gl-icon :name="allExpanded ? 'angle-up' : 'angle-down'" /> - </button> - </div> + <div class="line-resolve-all"> + <span :class="lineResolveClass"> + <template v-if="allResolved"> + <gl-icon name="check-circle-filled" /> + {{ __('All threads resolved') }} + </template> + <template v-else> + {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }} + </template> + </span> </div> + <gl-button-group> + <gl-button + v-if="resolveAllDiscussionsIssuePath && !allResolved" + v-gl-tooltip + :href="resolveAllDiscussionsIssuePath" + :title="s__('Resolve all threads in new issue')" + :aria-label="s__('Resolve all threads in new issue')" + class="new-issue-for-discussion discussion-create-issue-btn" + icon="issue-new" + /> + <gl-button + v-if="isLoggedIn && !allResolved" + v-gl-tooltip + :title="__('Jump to next unresolved thread')" + :aria-label="__('Jump to next unresolved thread')" + class="discussion-next-btn" + data-track-event="click_button" + data-track-label="mr_next_unresolved_thread" + data-track-property="click_next_unresolved_thread_top" + icon="comment-next" + @click="jumpToNextDiscussion" + /> + <gl-button + v-gl-tooltip + :title="toggleThreadsLabel" + :aria-label="toggleThreadsLabel" + class="toggle-all-discussions-btn" + :icon="allExpanded ? 'angle-up' : 'angle-down'" + @click="handleExpandDiscussions" + /> + </gl-button-group> </div> </template> diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index 989ce9ff144..0e7ed854032 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -1,7 +1,6 @@ <script> -import $ from 'jquery'; import { mapGetters, mapActions } from 'vuex'; -import { GlIcon } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui'; import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility'; import { DISCUSSION_FILTERS_DEFAULT_VALUE, @@ -14,7 +13,9 @@ import notesEventHub from '../event_hub'; export default { components: { - GlIcon, + GlDropdown, + GlDropdownItem, + GlDropdownDivider, }, props: { filters: { @@ -66,9 +67,6 @@ export default { selectFilter(value, persistFilter = true) { const filter = parseInt(value, 10); - // close dropdown - this.toggleDropdown(); - if (filter === this.currentValue) return; this.currentValue = filter; this.filterDiscussion({ @@ -78,9 +76,6 @@ export default { }); this.toggleCommentsForm(); }, - toggleDropdown() { - $(this.$refs.dropdownToggle).dropdown('toggle'); - }, toggleCommentsForm() { this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE); }, @@ -92,7 +87,6 @@ export default { if (/^note_/.test(hash) && this.currentValue !== DISCUSSION_FILTERS_DEFAULT_VALUE) { this.selectFilter(this.defaultValue, false); - this.toggleDropdown(); // close dropdown this.setTargetNoteHash(hash); } }, @@ -109,43 +103,24 @@ export default { </script> <template> - <div + <gl-dropdown v-if="displayFilters" - class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom full-width-mobile" + id="discussion-filter-dropdown" + class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container qa-discussion-filter" + :text="currentFilter.title" > - <button - id="discussion-filter-dropdown" - ref="dropdownToggle" - class="btn btn-sm qa-discussion-filter" - data-toggle="dropdown" - aria-expanded="false" - > - {{ currentFilter.title }} <gl-icon name="chevron-down" /> - </button> - <div - ref="dropdownMenu" - class="dropdown-menu dropdown-menu-selectable dropdown-menu-right" - aria-labelledby="discussion-filter-dropdown" - > - <div class="dropdown-content"> - <ul> - <li - v-for="filter in filters" - :key="filter.value" - :data-filter-type="filterType(filter.value)" - > - <button - :class="{ 'is-active': filter.value === currentValue }" - class="qa-filter-options" - type="button" - @click="selectFilter(filter.value)" - > - {{ filter.title }} - </button> - <div v-if="filter.value === defaultValue" class="dropdown-divider"></div> - </li> - </ul> - </div> + <div v-for="filter in filters" :key="filter.value" class="dropdown-item-wrapper"> + <gl-dropdown-item + :is-check-item="true" + :is-checked="filter.value === currentValue" + :class="{ 'is-active': filter.value === currentValue }" + :data-filter-type="filterType(filter.value)" + class="qa-filter-options" + @click.prevent="selectFilter(filter.value)" + > + {{ filter.title }} + </gl-dropdown-item> + <gl-dropdown-divider v-if="filter.value === defaultValue" /> </div> - </div> + </gl-dropdown> </template> diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index a8057276f1a..c2f40b2d21a 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -160,7 +160,7 @@ export default { }); }, displayMemberBadgeText() { - return sprintf(__('This user is a %{access} of the %{name} project.'), { + return sprintf(__('This user has the %{access} role in the %{name} project.'), { access: this.accessLevel.toLowerCase(), name: this.projectName, }); @@ -275,7 +275,8 @@ export default { v-gl-tooltip type="button" title="Edit comment" - class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button" + 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" /> diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index 314fa762768..65b89b94eaa 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -45,7 +45,7 @@ export default { }, }, computed: { - ...mapGetters(['getDiscussion']), + ...mapGetters(['getDiscussion', 'suggestionsCount']), discussion() { if (!this.note.isDraft) return {}; @@ -125,6 +125,7 @@ export default { <suggestions v-if="hasSuggestion && !isEditing" :suggestions="note.suggestions" + :suggestions-count="suggestionsCount" :batch-suggestions-info="batchSuggestionsInfo" :note-html="note.note_html" :line-type="lineType" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 88b4461cf38..4b3f23e742d 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -328,6 +328,7 @@ export default { :add-spacing-classes="false" :help-page-path="helpPagePath" :show-suggest-popover="showSuggestPopover" + :textarea-value="updatedNoteBody" @handleSuggestDismissed="() => $emit('handleSuggestDismissed')" > <textarea @@ -337,7 +338,8 @@ export default { 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 qa-reply-input" + 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…')" @@ -376,7 +378,8 @@ export default { <button :disabled="isDisabled" type="button" - class="btn btn-success qa-start-review" + class="btn btn-success" + data-qa-selector="start_review_button" @click="handleAddToReview" > <template v-if="hasDrafts">{{ __('Add to review') }}</template> @@ -385,7 +388,8 @@ export default { <button :disabled="isDisabled" type="button" - class="btn qa-comment-now js-comment-button" + class="btn js-comment-button" + data-qa-selector="comment_now_button" @click="handleUpdate()" > {{ __('Add comment now') }} @@ -404,7 +408,8 @@ export default { <button :disabled="isDisabled" type="button" - class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button" + class="js-vue-issue-save btn btn-success js-comment-button" + data-qa-selector="reply_comment_button" @click="handleUpdate()" > {{ saveButtonTitle }} diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue index 60b531d7597..113c00ffe8e 100644 --- a/app/assets/javascripts/notes/components/sort_discussion.vue +++ b/app/assets/javascripts/notes/components/sort_discussion.vue @@ -1,6 +1,5 @@ -gs <script> -import { GlIcon } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { mapActions, mapGetters } from 'vuex'; import { __ } from '~/locale'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; @@ -15,7 +14,8 @@ const SORT_OPTIONS = [ export default { SORT_OPTIONS, components: { - GlIcon, + GlDropdown, + GlDropdownItem, LocalStorageSync, }, mixins: [Tracking.mixin()], @@ -49,33 +49,27 @@ export default { </script> <template> - <div - data-testid="sort-discussion-filter" - class="gl-mr-2 gl-display-inline-block gl-vertical-align-bottom full-width-mobile" - > + <div class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile"> <local-storage-sync :value="sortDirection" :storage-key="storageKey" @input="setDiscussionSortDirection" /> - <button class="btn btn-sm js-dropdown-text" data-toggle="dropdown" aria-expanded="false"> - {{ dropdownText }} - <gl-icon name="chevron-down" /> - </button> - <div ref="dropdownMenu" class="dropdown-menu dropdown-menu-selectable dropdown-menu-right"> - <div class="dropdown-content"> - <ul> - <li v-for="{ text, key, cls } in $options.SORT_OPTIONS" :key="key"> - <button - :class="[cls, { 'is-active': isDropdownItemActive(key) }]" - type="button" - @click="fetchSortedDiscussions(key)" - > - {{ text }} - </button> - </li> - </ul> - </div> - </div> + <gl-dropdown + :text="dropdownText" + data-testid="sort-discussion-filter" + class="js-dropdown-text full-width-mobile" + > + <gl-dropdown-item + v-for="{ text, key, cls } in $options.SORT_OPTIONS" + :key="key" + :class="cls" + :is-check-item="true" + :is-checked="isDropdownItemActive(key)" + @click="fetchSortedDiscussions(key)" + > + {{ text }} + </gl-dropdown-item> + </gl-dropdown> </div> </template> diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index bddac60647d..f49fd2c3fa3 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -57,7 +57,12 @@ export default { tooltip-placement="bottom" /> </div> - <button class="btn btn-link js-replies-text qa-expand-replies" type="button" @click="toggle"> + <button + class="btn btn-link js-replies-text" + data-qa-selector="expand_replies_button" + type="button" + @click="toggle" + > {{ replies.length }} {{ n__('reply', 'replies', replies.length) }} </button> {{ __('Last reply by') }} @@ -68,7 +73,8 @@ export default { </template> <span v-else - class="collapse-replies-btn js-collapse-replies qa-collapse-replies" + class="collapse-replies-btn js-collapse-replies" + data-qa-selector="collapse_replies_button" @click="toggle" > <gl-icon name="chevron-down" /> {{ s__('Notes|Collapse replies') }} diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 7bf465482b3..ca186123a83 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -5,15 +5,19 @@ import initSortDiscussions from './sort_discussions'; import { store } from './stores'; document.addEventListener('DOMContentLoaded', () => { + const el = document.getElementById('js-vue-notes'); + + if (!el) return; + // eslint-disable-next-line no-new new Vue({ - el: '#js-vue-notes', + el, components: { notesApp, }, store, data() { - const notesDataset = document.getElementById('js-vue-notes').dataset; + const notesDataset = el.dataset; const parsedUserData = JSON.parse(notesDataset.currentUserData); const noteableData = JSON.parse(notesDataset.noteableData); let currentUserData = {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 7d60fbffb10..fbdb71925ea 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -231,3 +231,6 @@ export const getDiscussion = state => discussionId => state.discussions.find(discussion => discussion.id === discussionId); export const commentsDisabled = state => state.commentsDisabled; + +export const suggestionsCount = (state, getters) => + Object.values(getters.notesById).filter(n => n.suggestions.length).length; |