diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-06 00:09:04 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-06 00:09:04 +0300 |
commit | 96e23b2017cbe56969771960f6c274c5d3599397 (patch) | |
tree | b8b17da1ab080dd41fc64fc0262de2cf16754559 /app/assets/javascripts/design_management | |
parent | 2f1a81fd16ff9968d6b986f8a407d963bc2218f9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/design_management')
8 files changed, 270 insertions, 0 deletions
diff --git a/app/assets/javascripts/design_management/components/design_description/description_form.vue b/app/assets/javascripts/design_management/components/design_description/description_form.vue new file mode 100644 index 00000000000..890d7f80f8d --- /dev/null +++ b/app/assets/javascripts/design_management/components/design_description/description_form.vue @@ -0,0 +1,234 @@ +<script> +import { GlButton, GlFormGroup, GlAlert, GlTooltipDirective } from '@gitlab/ui'; + +import SafeHtml from '~/vue_shared/directives/safe_html'; +import { __, s__ } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; +import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; +import { toggleMarkCheckboxes } from '~/behaviors/markdown/utils'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + +import updateDesignDescriptionMutation from '../../graphql/mutations/update_design_description.mutation.graphql'; +import { UPDATE_DESCRIPTION_ERROR } from '../../utils/error_messages'; + +const isCheckbox = (target) => target?.classList.contains('task-list-item-checkbox'); + +export default { + components: { + MarkdownEditor, + GlAlert, + GlButton, + GlFormGroup, + }, + directives: { + SafeHtml, + GlTooltip: GlTooltipDirective, + }, + i18n: { + edit: __('Edit'), + editDescription: s__('DesignManagement|Edit description'), + descriptionLabel: s__('DesignManagement|Design description'), + }, + formFieldProps: { + id: 'design-description', + name: 'design-description', + placeholder: s__('DesignManagement|Write a comment or drag your files hereā¦'), + 'aria-label': s__('DesignManagement|Design description'), + }, + mixins: [glFeaturesFlagMixin()], + markdownDocsPath: helpPagePath('user/markdown'), + quickActionsDocsPath: helpPagePath('user/project/quick_actions'), + props: { + design: { + type: Object, + required: true, + }, + markdownPreviewPath: { + type: String, + required: true, + }, + designVariables: { + type: Object, + required: true, + }, + }, + data() { + return { + descriptionText: this.design.description || '', + showEditor: false, + isSubmitting: false, + errorMessage: '', + autosaveKey: `Issue/${getIdFromGraphQLId(this.design.issue.id)}/Design/${getIdFromGraphQLId( + this.design.id, + )}`, + }; + }, + computed: { + canUpdate() { + return this.design.issue?.userPermissions?.updateDesign && !this.showEditor; + }, + }, + watch: { + 'design.descriptionHtml': { + handler(newDescriptionHtml, oldDescriptionHtml) { + if (newDescriptionHtml !== oldDescriptionHtml) { + this.renderGFM(); + } + }, + immediate: true, + }, + }, + methods: { + startEditing() { + this.showEditor = true; + }, + closeForm() { + this.showEditor = false; + }, + async renderGFM() { + await this.$nextTick(); + renderGFM(this.$refs['gfm-content']); + + if (this.canUpdate) { + const checkboxes = this.$el.querySelectorAll('.task-list-item-checkbox'); + + // enable boxes, disabled by default in markdown + checkboxes.forEach((checkbox) => { + // eslint-disable-next-line no-param-reassign + checkbox.disabled = false; + }); + } + }, + setDescriptionText(newText) { + // Do not update when cmd+enter is executed + if (!this.isSubmitting) { + this.descriptionText = newText; + } + }, + async updateDesignDescription() { + this.isSubmitting = true; + + try { + const designDescriptionInput = { description: this.descriptionText, id: this.design.id }; + + await this.$apollo.mutate({ + mutation: updateDesignDescriptionMutation, + variables: { + input: designDescriptionInput, + }, + }); + + this.closeForm(); + } catch { + this.errorMessage = UPDATE_DESCRIPTION_ERROR; + } finally { + this.isSubmitting = false; + } + }, + toggleCheckboxes(event) { + const { target } = event; + + if (isCheckbox(target)) { + target.disabled = true; + + const { sourcepos } = target.parentElement.dataset; + + if (!sourcepos) return; + + // Toggle checkboxes based on user input + this.descriptionText = toggleMarkCheckboxes({ + rawMarkdown: this.descriptionText, + checkboxChecked: target.checked, + sourcepos, + }); + + // Update the desciption text using mutation + this.updateDesignDescription(); + } + }, + }, +}; +</script> + +<template> + <div class="design-description-container"> + <gl-form-group + v-if="showEditor" + class="design-description-form common-note-form" + :label="$options.i18n.descriptionLabel" + > + <div v-if="errorMessage" class="gl-pb-3"> + <gl-alert variant="danger" @dismiss="errorMessage = null"> + {{ errorMessage }} + </gl-alert> + </div> + <markdown-editor + :value="descriptionText" + :render-markdown-path="markdownPreviewPath" + :markdown-docs-path="$options.markdownDocsPath" + :form-field-props="$options.formFieldProps" + :enable-content-editor="Boolean(glFeatures.contentEditorOnIssues)" + :quick-actions-docs-path="$options.quickActionsDocsPath" + :autosave-key="autosaveKey" + enable-autocomplete + :supports-quick-actions="false" + autofocus + @input="setDescriptionText" + @keydown.meta.enter="updateDesignDescription" + @keydown.ctrl.enter="updateDesignDescription" + @keydown.exact.esc.stop="closeForm" + /> + <div class="gl-display-flex gl-mt-3"> + <gl-button + category="primary" + variant="confirm" + :loading="isSubmitting" + data-testid="save-description" + @click="updateDesignDescription" + >{{ s__('DesignManagement|Save') }} + </gl-button> + <gl-button category="tertiary" class="gl-ml-3" data-testid="cancel" @click="closeForm" + >{{ s__('DesignManagement|Cancel') }} + </gl-button> + </div> + </gl-form-group> + <div v-else class="design-description-view"> + <div + class="design-description-header gl-display-flex gl-justify-content-space-between gl-mb-2" + > + <label class="gl-m-0"> + {{ $options.i18n.descriptionLabel }} + </label> + <gl-button + v-if="canUpdate" + v-gl-tooltip + class="gl-ml-auto" + size="small" + data-testid="edit-description" + :aria-label="$options.i18n.editDescription" + @click="startEditing" + > + {{ $options.i18n.edit }} + </gl-button> + </div> + <div + v-if="!design.descriptionHtml" + data-testid="design-description-none" + class="gl-text-secondary gl-mb-5" + > + {{ s__('DesignManagement|None') }} + </div> + <div v-else class="design-description js-task-list-container"> + <div + ref="gfm-content" + v-safe-html="design.descriptionHtml" + class="md gl-mb-4" + data-testid="design-description-content" + @change="toggleCheckboxes" + ></div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue index c34d5cea0c2..9a8685f4c86 100644 --- a/app/assets/javascripts/design_management/components/design_sidebar.vue +++ b/app/assets/javascripts/design_management/components/design_sidebar.vue @@ -9,6 +9,7 @@ import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants'; import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql'; import { extractDiscussions, extractParticipants } from '../utils/design_management_utils'; import DesignDiscussion from './design_notes/design_discussion.vue'; +import DescriptionForm from './design_description/description_form.vue'; import DesignNoteSignedOut from './design_notes/design_note_signed_out.vue'; import DesignTodoButton from './design_todo_button.vue'; @@ -21,6 +22,7 @@ export default { GlAccordionItem, GlSkeletonLoader, DesignTodoButton, + DescriptionForm, }, mixins: [glFeatureFlagsMixin()], inject: { @@ -54,6 +56,10 @@ export default { type: Boolean, required: true, }, + designVariables: { + type: Object, + required: true, + }, }, data() { return { @@ -143,6 +149,12 @@ export default { :href="issue.webUrl" >{{ issue.webPath }}</a > + <description-form + v-if="!isLoading" + :design="design" + :design-variables="designVariables" + :markdown-preview-path="markdownPreviewPath" + /> <participants :participants="discussionParticipants" :show-participant-label="false" diff --git a/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql index 9bd70e7e886..575201a7635 100644 --- a/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql +++ b/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql @@ -5,6 +5,8 @@ fragment DesignListItem on Design { notesCount image imageV432x230 + description + descriptionHtml currentUserTodos(state: pending) { nodes { id diff --git a/app/assets/javascripts/design_management/graphql/mutations/update_design_description.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/update_design_description.mutation.graphql new file mode 100644 index 00000000000..78b66477747 --- /dev/null +++ b/app/assets/javascripts/design_management/graphql/mutations/update_design_description.mutation.graphql @@ -0,0 +1,11 @@ +mutation updateDesignDescriptionMutation($input: DesignManagementUpdateInput!) { + designManagementUpdate(input: $input) { + errors + design { + id + image + description + descriptionHtml + } + } +} diff --git a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql index 730467c33f6..c6eda2797d5 100644 --- a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql +++ b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql @@ -25,6 +25,10 @@ query getDesign( ...Author } } + userPermissions { + createDesign + updateDesign + } } } } diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index eeb36e59b89..65e04b1ff98 100644 --- a/app/assets/javascripts/design_management/pages/design/index.vue +++ b/app/assets/javascripts/design_management/pages/design/index.vue @@ -385,6 +385,7 @@ export default { </div> <design-sidebar :design="design" + :design-variables="designVariables" :resolved-discussions-expanded="resolvedDiscussionsExpanded" :markdown-preview-path="markdownPreviewPath" :is-loading="isLoading" diff --git a/app/assets/javascripts/design_management/utils/design_management_utils.js b/app/assets/javascripts/design_management/utils/design_management_utils.js index 7470f3d259b..2db34ea7103 100644 --- a/app/assets/javascripts/design_management/utils/design_management_utils.js +++ b/app/assets/javascripts/design_management/utils/design_management_utils.js @@ -61,6 +61,8 @@ export const designUploadOptimisticResponse = (files) => { id: -uniqueId(), image: '', imageV432x230: '', + description: '', + descriptionHtml: '', filename: file.name, fullPath: '', notesCount: 0, diff --git a/app/assets/javascripts/design_management/utils/error_messages.js b/app/assets/javascripts/design_management/utils/error_messages.js index 1ed054abe22..2b5d04959b4 100644 --- a/app/assets/javascripts/design_management/utils/error_messages.js +++ b/app/assets/javascripts/design_management/utils/error_messages.js @@ -138,3 +138,7 @@ export const MAXIMUM_FILE_UPLOAD_LIMIT_REACHED = sprintf( upload_limit: MAXIMUM_FILE_UPLOAD_LIMIT, }, ); + +export const UPDATE_DESCRIPTION_ERROR = s__( + 'DesignManagement|Could not update description. Please try again.', +); |