diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-20 18:38:24 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-20 18:38:24 +0000 |
commit | 983a0bba5d2a042c4a3bbb22432ec192c7501d82 (patch) | |
tree | b153cd387c14ba23bd5a07514c7c01fddf6a78a0 /app/assets/javascripts/vue_shared | |
parent | a2bddee2cdb38673df0e004d5b32d9f77797de64 (diff) | |
download | gitlab-ce-983a0bba5d2a042c4a3bbb22432ec192c7501d82.tar.gz |
Add latest changes from gitlab-org/gitlab@12-10-stable-ee
Diffstat (limited to 'app/assets/javascripts/vue_shared')
6 files changed, 191 insertions, 45 deletions
diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue new file mode 100644 index 00000000000..848295cc984 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/awards_list.vue @@ -0,0 +1,178 @@ +<script> +import { groupBy } from 'lodash'; +import { GlIcon } from '@gitlab/ui'; +import tooltip from '~/vue_shared/directives/tooltip'; +import { glEmojiTag } from '../../emoji'; +import { __, sprintf } from '~/locale'; + +// Internal constant, specific to this component, used when no `currentUserId` is given +const NO_USER_ID = -1; + +export default { + components: { + GlIcon, + }, + directives: { + tooltip, + }, + props: { + awards: { + type: Array, + required: true, + }, + canAwardEmoji: { + type: Boolean, + required: true, + }, + currentUserId: { + type: Number, + required: false, + default: NO_USER_ID, + }, + addButtonClass: { + type: String, + required: false, + default: '', + }, + }, + computed: { + groupedAwards() { + const { thumbsup, thumbsdown, ...rest } = groupBy(this.awards, x => x.name); + + return [ + ...(thumbsup ? [this.createAwardList('thumbsup', thumbsup)] : []), + ...(thumbsdown ? [this.createAwardList('thumbsdown', thumbsdown)] : []), + ...Object.entries(rest).map(([name, list]) => this.createAwardList(name, list)), + ]; + }, + isAuthoredByMe() { + return this.noteAuthorId === this.currentUserId; + }, + }, + methods: { + getAwardClassBindings(awardList) { + return { + active: this.hasReactionByCurrentUser(awardList), + disabled: this.currentUserId === NO_USER_ID, + }; + }, + hasReactionByCurrentUser(awardList) { + if (this.currentUserId === NO_USER_ID) { + return false; + } + + return awardList.some(award => award.user.id === this.currentUserId); + }, + createAwardList(name, list) { + return { + name, + list, + title: this.getAwardListTitle(list), + classes: this.getAwardClassBindings(list), + html: glEmojiTag(name), + }; + }, + getAwardListTitle(awardsList) { + const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList); + const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10; + let awardList = awardsList; + + // Filter myself from list if I am awarded. + if (hasReactionByCurrentUser) { + awardList = awardList.filter(award => award.user.id !== this.currentUserId); + } + + // Get only 9-10 usernames to show in tooltip text. + const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name); + + // Get the remaining list to use in `and x more` text. + const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length); + + // Add myself to the beginning of the list so title will start with You. + if (hasReactionByCurrentUser) { + namesToShow.unshift(__('You')); + } + + let title = ''; + + // We have 10+ awarded user, join them with comma and add `and x more`. + if (remainingAwardList.length) { + title = sprintf( + __(`%{listToShow}, and %{awardsListLength} more.`), + { + listToShow: namesToShow.join(', '), + awardsListLength: remainingAwardList.length, + }, + false, + ); + } else if (namesToShow.length > 1) { + // Join all names with comma but not the last one, it will be added with and text. + title = namesToShow.slice(0, namesToShow.length - 1).join(', '); + // If we have more than 2 users we need an extra comma before and text. + title += namesToShow.length > 2 ? ',' : ''; + title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }, false); // Append and text + } else { + // We have only 2 users so join them with and. + title = namesToShow.join(__(' and ')); + } + + return title; + }, + handleAward(awardName) { + if (!this.canAwardEmoji) { + return; + } + + // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string + const parsedName = /^[0-9]+$/.test(awardName) ? Number(awardName) : awardName; + + this.$emit('award', parsedName); + }, + }, +}; +</script> + +<template> + <div class="awards js-awards-block"> + <button + v-for="awardList in groupedAwards" + :key="awardList.name" + v-tooltip + :class="awardList.classes" + :title="awardList.title" + data-boundary="viewport" + data-testid="award-button" + class="btn award-control" + type="button" + @click="handleAward(awardList.name)" + > + <span data-testid="award-html" v-html="awardList.html"></span> + <span class="award-control-text js-counter">{{ awardList.list.length }}</span> + </button> + <div v-if="canAwardEmoji" class="award-menu-holder"> + <button + v-tooltip + :class="addButtonClass" + class="award-control btn js-add-award" + title="Add reaction" + :aria-label="__('Add reaction')" + data-boundary="viewport" + type="button" + > + <span class="award-control-icon award-control-icon-neutral"> + <gl-icon aria-hidden="true" name="slight-smile" /> + </span> + <span class="award-control-icon award-control-icon-positive"> + <gl-icon aria-hidden="true" name="smiley" /> + </span> + <span class="award-control-icon award-control-icon-super-positive"> + <gl-icon aria-hidden="true" name="smiley" /> + </span> + <i + aria-hidden="true" + class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading" + ></i> + </button> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue index 3b9b9f37f52..7826c179889 100644 --- a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue @@ -3,7 +3,7 @@ import { GlNewDropdown, GlNewDropdownHeader, GlFormInputGroup, - GlNewButton, + GlButton, GlIcon, GlTooltipDirective, } from '@gitlab/ui'; @@ -15,7 +15,7 @@ export default { GlNewDropdown, GlNewDropdownHeader, GlFormInputGroup, - GlNewButton, + GlButton, GlIcon, }, directives: { @@ -55,13 +55,13 @@ export default { <div class="mx-3"> <gl-form-input-group :value="sshLink" readonly select-on-click> <template #append> - <gl-new-button + <gl-button v-gl-tooltip.hover :title="$options.copyURLTooltip" :data-clipboard-text="sshLink" > <gl-icon name="copy-to-clipboard" :title="$options.copyURLTooltip" /> - </gl-new-button> + </gl-button> </template> </gl-form-input-group> </div> @@ -73,13 +73,13 @@ export default { <div class="mx-3"> <gl-form-input-group :value="httpLink" readonly select-on-click> <template #append> - <gl-new-button + <gl-button v-gl-tooltip.hover :title="$options.copyURLTooltip" :data-clipboard-text="httpLink" > <gl-icon name="copy-to-clipboard" :title="$options.copyURLTooltip" /> - </gl-new-button> + </gl-button> </template> </gl-form-input-group> </div> diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index cdcd5cdef7f..ffc616d7309 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -158,7 +158,7 @@ export default { <template> <tooltip-on-truncate :title="timeWindowText" - :truncate-target="elem => elem.querySelector('.date-time-picker-toggle')" + :truncate-target="elem => elem.querySelector('.gl-dropdown-toggle-text')" placement="top" class="d-inline-block" > diff --git a/app/assets/javascripts/vue_shared/components/form/title.vue b/app/assets/javascripts/vue_shared/components/form/title.vue index f8f70529bd1..fad69dc1e24 100644 --- a/app/assets/javascripts/vue_shared/components/form/title.vue +++ b/app/assets/javascripts/vue_shared/components/form/title.vue @@ -10,6 +10,6 @@ export default { </script> <template> <gl-form-group :label="__('Title')" label-for="title-field-edit"> - <gl-form-input id="title-field-edit" v-bind="$attrs" v-on="$listeners" /> + <gl-form-input v-bind="$attrs" v-on="$listeners" /> </gl-form-group> </template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue index 913c971a512..040a15406e0 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue @@ -37,7 +37,7 @@ export default { :title="tooltipLabel" :class="cssClasses" type="button" - class="btn btn-blank gutter-toggle btn-sidebar-action" + class="btn btn-blank gutter-toggle btn-sidebar-action js-sidebar-vue-toggle" data-container="body" data-placement="left" data-boundary="viewport" diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 602d4ab89e1..595baeeb14f 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -1,10 +1,8 @@ <script> -import { GlPopover, GlSkeletonLoading, GlSprintf } from '@gitlab/ui'; +import { GlPopover, GlSkeletonLoading } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; import { glEmojiTag } from '../../../emoji'; -import { s__ } from '~/locale'; -import { isString } from 'lodash'; export default { name: 'UserPopover', @@ -12,7 +10,6 @@ export default { Icon, GlPopover, GlSkeletonLoading, - GlSprintf, UserAvatarImage, }, props: { @@ -49,26 +46,7 @@ export default { return !this.user.name; }, workInformationIsLoading() { - return !this.user.loaded && this.workInformation === null; - }, - workInformation() { - const { jobTitle, organization } = this.user; - - if (organization && jobTitle) { - return { - message: s__('Profile|%{job_title} at %{organization}'), - placeholders: { job_title: jobTitle, organization }, - }; - } else if (organization) { - return organization; - } else if (jobTitle) { - return jobTitle; - } - - return null; - }, - workInformationShouldUseSprintf() { - return !isString(this.workInformation); + return !this.user.loaded && this.user.workInformation === null; }, locationIsLoading() { return !this.user.loaded && this.user.location === null; @@ -98,23 +76,13 @@ export default { <icon name="profile" class="category-icon flex-shrink-0" /> <span ref="bio" class="ml-1">{{ user.bio }}</span> </div> - <div v-if="workInformation" class="d-flex mb-1"> + <div v-if="user.workInformation" class="d-flex mb-1"> <icon v-show="!workInformationIsLoading" name="work" class="category-icon flex-shrink-0" /> - <span ref="workInformation" class="ml-1"> - <gl-sprintf v-if="workInformationShouldUseSprintf" :message="workInformation.message"> - <template - v-for="(placeholder, slotName) in workInformation.placeholders" - v-slot:[slotName] - > - <span :key="slotName">{{ placeholder }}</span> - </template> - </gl-sprintf> - <span v-else>{{ workInformation }}</span> - </span> + <span ref="workInformation" class="ml-1">{{ user.workInformation }}</span> </div> <gl-skeleton-loading v-if="workInformationIsLoading" |