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/design_management/components/design_notes/design_note.vue')
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note.vue153
1 files changed, 146 insertions, 7 deletions
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index 0eac2cad68d..1f2c9f19a95 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -7,14 +7,21 @@ import {
GlLink,
GlTooltipDirective,
} from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import { produce } from 'immer';
import SafeHtml from '~/vue_shared/directives/safe_html';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPENAME_USER } from '~/graphql_shared/constants';
import { __ } from '~/locale';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import EmojiPicker from '~/emoji/components/picker.vue';
+import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import updateNoteMutation from '../../graphql/mutations/update_note.mutation.graphql';
+import designNoteAwardEmojiToggleMutation from '../../graphql/mutations/design_note_award_emoji_toggle.mutation.graphql';
import { hasErrors } from '../../utils/cache_update';
import { findNoteId, extractDesignNoteId } from '../../utils/design_management_utils';
+import DesignNoteAwardsList from './design_note_awards_list.vue';
import DesignReplyForm from './design_reply_form.vue';
export default {
@@ -24,7 +31,9 @@ export default {
deleteCommentText: __('Delete comment'),
},
components: {
+ DesignNoteAwardsList,
DesignReplyForm,
+ EmojiPicker,
GlAvatar,
GlAvatarLink,
GlButton,
@@ -37,6 +46,7 @@ export default {
GlTooltip: GlTooltipDirective,
SafeHtml,
},
+ inject: ['issueIid', 'projectPath'],
props: {
note: {
type: Object,
@@ -56,6 +66,10 @@ export default {
type: String,
required: true,
},
+ designVariables: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
@@ -64,6 +78,26 @@ export default {
};
},
computed: {
+ currentUserId() {
+ return window.gon.current_user_id;
+ },
+ currentUserFullName() {
+ return window.gon.current_user_fullname;
+ },
+ canAwardEmoji() {
+ return this.note.userPermissions.awardEmoji;
+ },
+ awards() {
+ return this.note.awardEmoji.nodes.map((award) => {
+ return {
+ ...award,
+ user: {
+ ...award.user,
+ id: getIdFromGraphQLId(award.user.id),
+ },
+ };
+ });
+ },
author() {
return this.note.author;
},
@@ -124,6 +158,93 @@ export default {
this.$emit('error', data.errors[0]);
}
},
+ isEmojiPresentForCurrentUser(name) {
+ return (
+ this.awards.findIndex(
+ (emoji) => emoji.name === name && emoji.user.id === this.currentUserId,
+ ) > -1
+ );
+ },
+ /**
+ * Prepare award emoji nodes based on emoji name
+ * and whether the user has toggled the emoji off or on
+ */
+ getAwardEmojiNodes(name, toggledOn) {
+ // If the emoji toggled on, add the emoji
+ if (toggledOn) {
+ // If emoji is already present in award list, no action is needed
+ if (this.isEmojiPresentForCurrentUser(name)) {
+ return this.note.awardEmoji.nodes;
+ }
+
+ // else make a copy of unmutable list and return the list after adding the new emoji
+ const awardEmojiNodes = [...this.note.awardEmoji.nodes];
+ awardEmojiNodes.push({
+ name,
+ __typename: 'AwardEmoji',
+ user: {
+ id: convertToGraphQLId(TYPENAME_USER, this.currentUserId),
+ name: this.currentUserFullName,
+ __typename: 'UserCore',
+ },
+ });
+
+ return awardEmojiNodes;
+ }
+
+ // else just filter the emoji
+ return this.note.awardEmoji.nodes.filter(
+ (emoji) =>
+ !(emoji.name === name && getIdFromGraphQLId(emoji.user.id) === this.currentUserId),
+ );
+ },
+ handleAwardEmoji(name) {
+ this.$apollo
+ .mutate({
+ mutation: designNoteAwardEmojiToggleMutation,
+ variables: {
+ name,
+ awardableId: this.note.id,
+ },
+ optimisticResponse: {
+ awardEmojiToggle: {
+ errors: [],
+ toggledOn: !this.isEmojiPresentForCurrentUser(name),
+ },
+ },
+ update: (
+ cache,
+ {
+ data: {
+ awardEmojiToggle: { toggledOn },
+ },
+ },
+ ) => {
+ const query = {
+ query: getDesignQuery,
+ variables: this.designVariables,
+ };
+
+ const sourceData = cache.readQuery(query);
+
+ const newData = produce(sourceData, (draftState) => {
+ const {
+ awardEmoji,
+ } = draftState.project.issue.designCollection.designs.nodes[0].discussions.nodes
+ .find((d) => d.id === this.note.discussion.id)
+ .notes.nodes.find((n) => n.id === this.note.id);
+
+ awardEmoji.nodes = this.getAwardEmojiNodes(name, toggledOn);
+ });
+
+ cache.writeQuery({ ...query, data: newData });
+ },
+ })
+ .catch((error) => {
+ Sentry.captureException(error);
+ this.$emit('error', error);
+ });
+ },
},
updateNoteMutation,
};
@@ -131,7 +252,12 @@ export default {
<template>
<timeline-entry-item :id="`note_${noteAnchorId}`" class="design-note note-form">
- <gl-avatar-link :href="author.webUrl" class="gl-float-left gl-mr-3">
+ <gl-avatar-link
+ :href="author.webUrl"
+ :data-user-id="authorId"
+ :data-username="author.username"
+ class="gl-float-left gl-mr-3 link-inherit-color js-user-link"
+ >
<gl-avatar :size="32" :src="author.avatarUrl" :entity-name="author.username" />
</gl-avatar-link>
@@ -140,7 +266,7 @@ export default {
<gl-link
v-once
:href="author.webUrl"
- class="js-user-link"
+ class="js-user-link link-inherit-color"
data-testid="user-link"
:data-user-id="authorId"
:data-username="author.username"
@@ -152,15 +278,23 @@ export default {
<span class="note-headline-light note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<gl-link
- class="note-timestamp system-note-separator gl-display-block gl-mb-2 gl-font-sm"
+ class="note-timestamp system-note-separator gl-display-block gl-mb-2 gl-font-sm link-inherit-color"
:href="`#note_${noteAnchorId}`"
>
<time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" />
</gl-link>
</span>
</div>
- <div class="gl-display-flex gl-align-items-baseline gl-mt-n2 gl-mr-n2">
+ <div class="gl-display-flex gl-align-items-flex-start gl-mt-n2 gl-mr-n2">
<slot name="resolve-discussion"></slot>
+ <emoji-picker
+ v-if="canAwardEmoji"
+ toggle-class="note-action-button note-emoji-button btn-icon btn-default-tertiary"
+ boundary="viewport"
+ :right="false"
+ data-testid="note-emoji-button"
+ @click="handleAwardEmoji"
+ />
<gl-button
v-if="isEditingAndHasPermissions"
v-gl-tooltip
@@ -175,7 +309,6 @@ export default {
<gl-disclosure-dropdown
v-if="isEditingAndHasPermissions"
v-gl-tooltip.hover
- toggle-class="btn-sm"
icon="ellipsis_v"
category="tertiary"
data-qa-selector="design_discussion_actions_ellipsis_dropdown"
@@ -198,8 +331,14 @@ export default {
></div>
<slot name="resolved-status"></slot>
</template>
+ <design-note-awards-list
+ v-if="awards.length"
+ :awards="awards"
+ :can-award-emoji="note.userPermissions.awardEmoji"
+ @award="handleAwardEmoji"
+ />
<design-reply-form
- v-else
+ v-if="isEditing"
:markdown-preview-path="markdownPreviewPath"
:design-note-mutation="$options.updateNoteMutation"
:mutation-variables="mutationVariables"