summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/issues/show/components/description.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/issues/show/components/description.vue')
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue143
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>