diff options
Diffstat (limited to 'app/assets/javascripts/issues/show/components/description.vue')
-rw-r--r-- | app/assets/javascripts/issues/show/components/description.vue | 143 |
1 files changed, 78 insertions, 65 deletions
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 68ed7bb4062..0b7e128c47b 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -2,13 +2,18 @@ import { GlSafeHtmlDirective as SafeHtml, GlModal, + GlToast, + GlTooltip, GlModalDirective, - GlPopover, - GlButton, } from '@gitlab/ui'; import $ from 'jquery'; +import Vue from 'vue'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_WORK_ITEM } from '~/graphql_shared/constants'; import createFlash from '~/flash'; -import { __, sprintf } from '~/locale'; +import { isPositiveInteger } from '~/lib/utils/number_utils'; +import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility'; +import { __, s__, sprintf } from '~/locale'; import TaskList from '~/task_list'; import Tracking from '~/tracking'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -16,6 +21,8 @@ import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal. import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; import animateMixin from '../mixins/animate'; +Vue.use(GlToast); + export default { directives: { SafeHtml, @@ -23,9 +30,8 @@ export default { }, components: { GlModal, - GlPopover, CreateWorkItem, - GlButton, + GlTooltip, WorkItemDetailModal, }, mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()], @@ -63,15 +69,24 @@ export default { required: false, default: 0, }, + issueId: { + type: Number, + required: false, + default: null, + }, }, data() { + const workItemId = getParameterByName('work_item_id'); + return { preAnimation: false, pulseAnimation: false, initialUpdate: true, taskButtons: [], activeTask: {}, - workItemId: null, + workItemId: isPositiveInteger(workItemId) + ? convertToGraphQLId(TYPE_WORK_ITEM, workItemId) + : undefined, }; }, computed: { @@ -81,6 +96,9 @@ export default { workItemsEnabled() { return this.glFeatures.workItems; }, + issueGid() { + return this.issueId ? convertToGraphQLId(TYPE_WORK_ITEM, this.issueId) : null; + }, }, watch: { descriptionHtml(newDescription, oldDescription) { @@ -92,6 +110,9 @@ export default { this.$nextTick(() => { this.renderGFM(); + if (this.workItemsEnabled) { + this.renderTaskActions(); + } }); }, taskStatus() { @@ -168,9 +189,25 @@ export default { return; } + this.taskButtons = []; const taskListFields = this.$el.querySelectorAll('.task-list-item'); taskListFields.forEach((item, index) => { + const taskLink = item.querySelector('.gfm-issue'); + if (taskLink) { + const { issue, referenceType } = taskLink.dataset; + taskLink.addEventListener('click', (e) => { + e.preventDefault(); + this.workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue); + this.updateWorkItemIdUrlQuery(issue); + this.track('viewed_work_item_from_modal', { + category: 'workItems:show', + label: 'work_item_view', + property: `type_${referenceType}`, + }); + }); + return; + } const button = document.createElement('button'); button.classList.add( 'btn', @@ -188,59 +225,44 @@ export default { this.taskButtons.push(button.id); button.innerHTML = ` <svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14"> - <use href="${gon.sprite_icons}#ellipsis_v"></use> + <use href="${gon.sprite_icons}#doc-new"></use> </svg> `; + button.setAttribute('aria-label', s__('WorkItem|Convert to work item')); + button.addEventListener('click', () => this.openCreateTaskModal(button.id)); item.prepend(button); }); }, openCreateTaskModal(id) { - this.activeTask = { id, title: this.$el.querySelector(`#${id}`).parentElement.innerText }; + const { parentElement } = this.$el.querySelector(`#${id}`); + const lineNumbers = parentElement.getAttribute('data-sourcepos').match(/\b\d+(?=:)/g); + this.activeTask = { + id, + title: parentElement.innerText, + lineNumberStart: lineNumbers[0], + lineNumberEnd: lineNumbers[1], + }; this.$refs.modal.show(); }, closeCreateTaskModal() { this.$refs.modal.hide(); }, closeWorkItemDetailModal() { - this.workItemId = null; + this.workItemId = undefined; + this.updateWorkItemIdUrlQuery(undefined); }, - handleWorkItemDetailModalError(message) { - createFlash({ message }); - }, - handleCreateTask({ id, title, type }) { - const listItem = this.$el.querySelector(`#${this.activeTask.id}`).parentElement; - const taskBadge = document.createElement('span'); - taskBadge.innerHTML = ` - <svg data-testid="issue-open-m-icon" role="img" aria-hidden="true" class="gl-icon gl-fill-green-500 s12"> - <use href="${gon.sprite_icons}#issue-open-m"></use> - </svg> - <span class="badge badge-info badge-pill gl-badge sm gl-mr-1"> - ${__('Task')} - </span> - `; - const button = this.createWorkItemDetailButton(id, title, type); - taskBadge.append(button); - - listItem.insertBefore(taskBadge, listItem.lastChild); - listItem.removeChild(listItem.lastChild); + handleCreateTask(description) { + this.$emit('updateDescription', description); this.closeCreateTaskModal(); }, - createWorkItemDetailButton(id, title, type) { - const button = document.createElement('button'); - button.addEventListener('click', () => { - this.workItemId = id; - this.track('viewed_work_item_from_modal', { - category: 'workItems:show', - label: 'work_item_view', - property: `type_${type}`, - }); - }); - button.classList.add('btn-link'); - button.innerText = title; - return button; + handleDeleteTask() { + this.$toast.show(s__('WorkItem|Work item deleted')); }, - focusButton() { - this.$refs.convertButton[0].$el.focus(); + updateWorkItemIdUrlQuery(workItemId) { + updateHistory({ + url: setUrlParams({ work_item_id: workItemId }), + replace: true, + }); }, }, safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] }, @@ -266,17 +288,17 @@ export default { }" class="md" ></div> - <!-- eslint-disable vue/no-mutating-props --> + <textarea v-if="descriptionText" - v-model="descriptionText" + :value="descriptionText" :data-update-url="updateUrl" class="hidden js-task-list-field" dir="auto" data-testid="textarea" > </textarea> - <!-- eslint-enable vue/no-mutating-props --> + <gl-modal ref="modal" modal-id="create-task-modal" @@ -285,36 +307,27 @@ export default { body-class="gl-p-0!" > <create-work-item - :is-modal="true" + is-modal :initial-title="activeTask.title" + :issue-gid="issueGid" + :lock-version="lockVersion" + :line-number-start="activeTask.lineNumberStart" + :line-number-end="activeTask.lineNumberEnd" @closeModal="closeCreateTaskModal" @onCreate="handleCreateTask" /> </gl-modal> <work-item-detail-modal + :can-update="canUpdate" :visible="showWorkItemDetailModal" :work-item-id="workItemId" + @workItemDeleted="handleDeleteTask" @close="closeWorkItemDetailModal" - @error="handleWorkItemDetailModalError" /> <template v-if="workItemsEnabled"> - <gl-popover - v-for="item in taskButtons" - :key="item" - :target="item" - placement="top" - triggers="focus" - @shown="focusButton" - > - <gl-button - ref="convertButton" - variant="link" - data-testid="convert-to-task" - class="gl-text-gray-900! gl-text-decoration-none! gl-outline-0!" - @click="openCreateTaskModal(item)" - >{{ s__('WorkItem|Convert to work item') }}</gl-button - > - </gl-popover> + <gl-tooltip v-for="item in taskButtons" :key="item" :target="item"> + {{ s__('WorkItem|Convert to work item') }} + </gl-tooltip> </template> </div> </template> |