diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-06 06:10:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-06 06:10:22 +0300 |
commit | b1e7ea9111b4c466c30202464ad3172219e865a9 (patch) | |
tree | ec28f239942c7ac7ef8f45fdea2642a7fc42f3ad /app/assets/javascripts/boards | |
parent | bc439e2eed92ed397ac40e5f09a1b36cf42b2c83 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/boards')
4 files changed, 236 insertions, 5 deletions
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue index ce267be6d45..b4fe16de695 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue @@ -14,6 +14,16 @@ export default { required: false, default: false, }, + toggleHeader: { + type: Boolean, + required: false, + default: false, + }, + handleOffClick: { + type: Boolean, + required: false, + default: true, + }, }, inject: ['canUpdate'], data() { @@ -21,13 +31,25 @@ export default { edit: false, }; }, + computed: { + showHeader() { + if (!this.toggleHeader) { + return true; + } + + return !this.edit; + }, + }, destroyed() { window.removeEventListener('click', this.collapseWhenOffClick); }, methods: { collapseWhenOffClick({ target }) { if (!this.$el.contains(target)) { - this.collapse(); + this.$emit('off-click'); + if (this.handleOffClick) { + this.collapse(); + } } }, expand() { @@ -63,21 +85,26 @@ export default { <template> <div> - <div class="gl-display-flex gl-justify-content-space-between gl-mb-3"> + <header + v-show="showHeader" + class="gl-display-flex gl-justify-content-space-between gl-align-items-flex-start gl-mb-3" + > <span class="gl-vertical-align-middle"> - <span data-testid="title">{{ title }}</span> + <slot name="title"> + <span data-testid="title">{{ title }}</span> + </slot> <gl-loading-icon v-if="loading" inline class="gl-ml-2" /> </span> <gl-button v-if="canUpdate" variant="link" - class="gl-text-gray-900! js-sidebar-dropdown-toggle" + class="gl-text-gray-900! gl-ml-5 js-sidebar-dropdown-toggle" data-testid="edit-button" @click="toggle" > {{ __('Edit') }} </gl-button> - </div> + </header> <div v-show="!edit" class="gl-text-gray-500" data-testid="collapsed-content"> <slot name="collapsed">{{ __('None') }}</slot> </div> diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue new file mode 100644 index 00000000000..d0e641daf5c --- /dev/null +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue @@ -0,0 +1,171 @@ +<script> +import { mapGetters, mapActions } from 'vuex'; +import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; +import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; +import { joinPaths } from '~/lib/utils/url_utility'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; + +export default { + components: { + GlForm, + GlAlert, + GlButton, + GlFormGroup, + GlFormInput, + BoardEditableItem, + }, + directives: { + autofocusonshow, + }, + data() { + return { + title: '', + loading: false, + showChangesAlert: false, + }; + }, + computed: { + ...mapGetters({ issue: 'activeIssue' }), + pendingChangesStorageKey() { + return this.getPendingChangesKey(this.issue); + }, + projectPath() { + const referencePath = this.issue.referencePath || ''; + return referencePath.slice(0, referencePath.indexOf('#')); + }, + validationState() { + return Boolean(this.title); + }, + }, + watch: { + issue: { + handler(updatedIssue, formerIssue) { + if (formerIssue?.title !== this.title) { + localStorage.setItem(this.getPendingChangesKey(formerIssue), this.title); + } + + this.title = updatedIssue.title; + this.setPendingState(); + }, + immediate: true, + }, + }, + methods: { + ...mapActions(['setActiveIssueTitle']), + getPendingChangesKey(issue) { + if (!issue) { + return ''; + } + + return joinPaths( + window.location.pathname.slice(1), + String(issue.id), + 'issue-title-pending-changes', + ); + }, + async setPendingState() { + const pendingChanges = localStorage.getItem(this.pendingChangesStorageKey); + + if (pendingChanges) { + this.title = pendingChanges; + this.showChangesAlert = true; + await this.$nextTick(); + this.$refs.sidebarItem.expand(); + } else { + this.showChangesAlert = false; + } + }, + cancel() { + this.title = this.issue.title; + this.$refs.sidebarItem.collapse(); + this.showChangesAlert = false; + localStorage.removeItem(this.pendingChangesStorageKey); + }, + async setTitle() { + this.$refs.sidebarItem.collapse(); + + if (!this.title || this.title === this.issue.title) { + return; + } + + try { + this.loading = true; + await this.setActiveIssueTitle({ title: this.title, projectPath: this.projectPath }); + localStorage.removeItem(this.pendingChangesStorageKey); + this.showChangesAlert = false; + } catch (e) { + this.title = this.issue.title; + createFlash({ message: this.$options.i18n.updateTitleError }); + } finally { + this.loading = false; + } + }, + handleOffClick() { + if (this.title !== this.issue.title) { + this.showChangesAlert = true; + localStorage.setItem(this.pendingChangesStorageKey, this.title); + } else { + this.$refs.sidebarItem.collapse(); + } + }, + }, + i18n: { + issueTitlePlaceholder: __('Issue title'), + submitButton: __('Save changes'), + cancelButton: __('Cancel'), + updateTitleError: __('An error occurred when updating the issue title'), + invalidFeedback: __('An issue title is required'), + reviewYourChanges: __('Changes to the title have not been saved'), + }, +}; +</script> + +<template> + <board-editable-item + ref="sidebarItem" + toggle-header + :loading="loading" + :handle-off-click="false" + @off-click="handleOffClick" + > + <template #title> + <span class="gl-font-weight-bold" data-testid="issue-title">{{ issue.title }}</span> + </template> + <template #collapsed> + <span class="gl-text-gray-800">{{ issue.referencePath }}</span> + </template> + <template> + <gl-alert v-if="showChangesAlert" variant="warning" class="gl-mb-5" :dismissible="false"> + {{ $options.i18n.reviewYourChanges }} + </gl-alert> + <gl-form @submit.prevent="setTitle"> + <gl-form-group :invalid-feedback="$options.i18n.invalidFeedback" :state="validationState"> + <gl-form-input + v-model="title" + v-autofocusonshow + :placeholder="$options.i18n.issueTitlePlaceholder" + :state="validationState" + /> + </gl-form-group> + + <div class="gl-display-flex gl-w-full gl-justify-content-space-between gl-mt-5"> + <gl-button + variant="success" + size="small" + data-testid="submit-button" + :disabled="!title" + @click="setTitle" + > + {{ $options.i18n.submitButton }} + </gl-button> + + <gl-button size="small" data-testid="cancel-button" @click="cancel"> + {{ $options.i18n.cancelButton }} + </gl-button> + </div> + </gl-form> + </template> + </board-editable-item> +</template> diff --git a/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql new file mode 100644 index 00000000000..62e6c1352a6 --- /dev/null +++ b/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql @@ -0,0 +1,8 @@ +mutation issueSetTitle($input: UpdateIssueInput!) { + updateIssue(input: $input) { + issue { + title + } + errors + } +} diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 5783e87ba0a..e64c82f0342 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -27,6 +27,7 @@ import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.graphql'; import issueSetSubscriptionMutation from '../graphql/issue_set_subscription.mutation.graphql'; import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.graphql'; +import issueSetTitleMutation from '../graphql/issue_set_title.mutation.graphql'; const notImplemented = () => { /* eslint-disable-next-line @gitlab/require-i18n-strings */ @@ -472,6 +473,30 @@ export default { }); }, + setActiveIssueTitle: async ({ commit, getters }, input) => { + const { activeIssue } = getters; + const { data } = await gqlClient.mutate({ + mutation: issueSetTitleMutation, + variables: { + input: { + iid: String(activeIssue.iid), + projectPath: input.projectPath, + title: input.title, + }, + }, + }); + + if (data.updateIssue?.errors?.length > 0) { + throw new Error(data.updateIssue.errors); + } + + commit(types.UPDATE_ISSUE_BY_ID, { + issueId: activeIssue.id, + prop: 'title', + value: data.updateIssue.issue.title, + }); + }, + fetchBacklog: () => { notImplemented(); }, |