diff options
Diffstat (limited to 'app/assets/javascripts/issues/show/components/app.vue')
-rw-r--r-- | app/assets/javascripts/issues/show/components/app.vue | 202 |
1 files changed, 71 insertions, 131 deletions
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index 26c3db647a3..d59692d2a28 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -1,49 +1,36 @@ <script> -import { GlIcon, GlBadge, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui'; import Visibility from 'visibilityjs'; import { createAlert } from '~/alert'; -import { - issuableStatusText, - STATUS_CLOSED, - TYPE_EPIC, - TYPE_INCIDENT, - TYPE_ISSUE, - WORKSPACE_PROJECT, -} from '~/issues/constants'; +import { TYPE_EPIC, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; +import updateDescription from '~/issues/show/utils/update_description'; +import { sanitize } from '~/lib/dompurify'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import Poll from '~/lib/utils/poll'; +import { containsSensitiveToken, confirmSensitiveAction, i18n } from '~/lib/utils/secret_detection'; import { visitUrl } from '~/lib/utils/url_utility'; import { __, sprintf } from '~/locale'; -import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; -import { containsSensitiveToken, confirmSensitiveAction, i18n } from '~/lib/utils/secret_detection'; import { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, POLLING_DELAY } from '../constants'; import eventHub from '../event_hub'; import getIssueStateQuery from '../queries/get_issue_state.query.graphql'; import Service from '../services/index'; -import Store from '../stores'; import DescriptionComponent from './description.vue'; import EditedComponent from './edited.vue'; import FormComponent from './form.vue'; import HeaderActions from './header_actions.vue'; import IssueHeader from './issue_header.vue'; import PinnedLinks from './pinned_links.vue'; +import StickyHeader from './sticky_header.vue'; import TitleComponent from './title.vue'; export default { - WORKSPACE_PROJECT, components: { - GlIcon, - GlBadge, - GlIntersectionObserver, HeaderActions, IssueHeader, TitleComponent, EditedComponent, FormComponent, PinnedLinks, - ConfidentialityBadge, - }, - directives: { - GlTooltip: GlTooltipDirective, + StickyHeader, }, props: { author: { @@ -234,21 +221,26 @@ export default { }, }, data() { - const store = new Store({ - titleHtml: this.initialTitleHtml, - titleText: this.initialTitleText, - descriptionHtml: this.initialDescriptionHtml, - descriptionText: this.initialDescriptionText, - updatedAt: this.updatedAt, - updatedByName: this.updatedByName, - updatedByPath: this.updatedByPath, - taskCompletionStatus: this.initialTaskCompletionStatus, - lock_version: this.lockVersion, - }); - return { - store, - state: store.state, + formState: { + title: '', + description: '', + lockedWarningVisible: false, + updateLoading: false, + lock_version: 0, + issuableTemplates: {}, + }, + state: { + titleHtml: this.initialTitleHtml, + titleText: this.initialTitleText, + descriptionHtml: this.initialDescriptionHtml, + descriptionText: this.initialDescriptionText, + updatedAt: this.updatedAt, + updatedByName: this.updatedByName, + updatedByPath: this.updatedByPath, + taskCompletionStatus: this.initialTaskCompletionStatus, + lock_version: this.lockVersion, + }, showForm: false, templatesRequested: false, isStickyHeaderShowing: false, @@ -264,17 +256,9 @@ export default { headerClasses() { return this.issuableType === TYPE_INCIDENT ? 'gl-mb-3' : 'gl-mb-6'; }, - issuableTemplates() { - return this.store.formState.issuableTemplates; - }, - formState() { - return this.store.formState; - }, issueChanged() { const { - store: { - formState: { description, title }, - }, + formState: { description, title }, initialDescriptionText, initialTitleText, } = this; @@ -292,26 +276,13 @@ export default { defaultErrorMessage() { return sprintf(__('Error updating %{issuableType}'), { issuableType: this.issuableType }); }, - isClosed() { - return this.issuableStatus === STATUS_CLOSED; - }, + pinnedLinkClasses() { return this.showTitleBorder ? 'gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6' : ''; }, - statusIcon() { - if (this.issuableType === TYPE_EPIC) { - return this.isClosed ? 'epic-closed' : 'epic'; - } - return this.isClosed ? 'issue-closed' : 'issues'; - }, - statusVariant() { - return this.isClosed ? 'info' : 'success'; - }, - statusText() { - return issuableStatusText[this.issuableStatus]; - }, + shouldShowStickyHeader() { return [TYPE_INCIDENT, TYPE_ISSUE, TYPE_EPIC].includes(this.issuableType); }, @@ -322,7 +293,7 @@ export default { this.poll = new Poll({ resource: this.service, method: 'getData', - successCallback: (res) => this.store.updateState(res.data), + successCallback: (res) => this.updateState(res.data), errorCallback(err) { throw new Error(err); }, @@ -360,23 +331,37 @@ export default { } return undefined; }, + updateState(data) { + const stateShouldUpdate = + this.state.titleText !== data.title_text || + this.state.descriptionText !== data.description_text; - updateStoreState() { + if (stateShouldUpdate) { + this.formState.lockedWarningVisible = true; + } + + Object.assign(this.state, convertObjectPropsToCamelCase(data)); + // find if there is an open details node inside of the issue description. + const descriptionSection = document.body.querySelector( + '.detail-page-description.content-block', + ); + const details = + descriptionSection != null && descriptionSection.getElementsByTagName('details'); + + this.state.descriptionHtml = updateDescription(sanitize(data.description), details); + this.state.titleHtml = sanitize(data.title); + this.state.lock_version = data.lock_version; + }, + refetchData() { return this.service .getData() .then((res) => res.data) - .then((data) => { - this.store.updateState(data); - }) - .catch(() => { - createAlert({ - message: this.defaultErrorMessage, - }); - }); + .then(this.updateState) + .catch(() => createAlert({ message: this.defaultErrorMessage })); }, setFormState(state) { - this.store.setFormState(state); + this.formState = { ...this.formState, ...state }; }, updateFormState(templates = {}) { @@ -416,7 +401,7 @@ export default { this.templatesRequested = true; this.requestTemplatesAndShowForm(); } else { - this.updateAndShowForm(this.issuableTemplates); + this.updateAndShowForm(this.formState.issuableTemplates); } }, @@ -427,10 +412,7 @@ export default { async updateIssuable() { this.setFormState({ updateLoading: true }); - const { - store: { formState }, - issueState, - } = this; + const { formState, issueState } = this; const issuablePayload = issueState.isDirty ? { ...formState, issue_type: issueState.issueType } : formState; @@ -464,7 +446,7 @@ export default { visitUrl(URI); } }) - .then(this.updateStoreState) + .then(this.refetchData) .then(() => { eventHub.$emit('close.form'); }) @@ -518,7 +500,7 @@ export default { this.poll.enable(); this.poll.makeDelayedRequest(POLLING_DELAY); - this.updateStoreState(); + this.refetchData(); }, }, }; @@ -531,7 +513,7 @@ export default { :endpoint="endpoint" :form-state="formState" :initial-description-text="initialDescriptionText" - :issuable-templates="issuableTemplates" + :issuable-templates="formState.issuableTemplates" :markdown-docs-path="markdownDocsPath" :markdown-preview-path="markdownPreviewPath" :project-path="projectPath" @@ -559,61 +541,19 @@ export default { </template> </title-component> - <gl-intersection-observer + <sticky-header v-if="shouldShowStickyHeader" - @appear="hideStickyHeader" - @disappear="showStickyHeader" - > - <transition name="issuable-header-slide"> - <div - v-if="isStickyHeaderShowing" - class="issue-sticky-header gl-fixed gl-z-index-3 gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-100 gl-py-3" - data-testid="issue-sticky-header" - > - <div - class="issue-sticky-header-text gl-display-flex gl-align-items-center gl-mx-auto gl-px-5" - > - <gl-badge :variant="statusVariant" class="gl-mr-2"> - <gl-icon :name="statusIcon" /> - <span class="gl-display-none gl-sm-display-block gl-ml-2">{{ - statusText - }}</span></gl-badge - > - <span - v-if="isLocked" - v-gl-tooltip.bottom - data-testid="locked" - class="issuable-warning-icon" - :title="__('This issue is locked. Only project members can comment.')" - > - <gl-icon name="lock" :aria-label="__('Locked')" /> - </span> - <confidentiality-badge - v-if="isConfidential" - data-testid="confidential" - :workspace-type="$options.WORKSPACE_PROJECT" - :issuable-type="issuableType" - /> - <span - v-if="isHidden" - v-gl-tooltip.bottom - :title="__('This issue is hidden because its author has been banned')" - data-testid="hidden" - class="issuable-warning-icon" - > - <gl-icon name="spam" /> - </span> - <a - href="#top" - class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-text-black-normal" - :title="state.titleText" - > - {{ state.titleText }} - </a> - </div> - </div> - </transition> - </gl-intersection-observer> + :is-confidential="isConfidential" + :is-hidden="isHidden" + :is-locked="isLocked" + :issuable-status="issuableStatus" + :issuable-type="issuableType" + :show="isStickyHeaderShowing" + :title="state.titleText" + :title-html="state.titleHtml" + @hide="hideStickyHeader" + @show="showStickyHeader" + /> <slot name="header"> <issue-header |