2017-09-10 17:25:29 +05:30
|
|
|
<script>
|
2022-04-04 11:22:00 +05:30
|
|
|
import {
|
|
|
|
GlSafeHtmlDirective as SafeHtml,
|
|
|
|
GlModal,
|
2022-06-21 17:19:12 +05:30
|
|
|
GlToast,
|
|
|
|
GlTooltip,
|
2022-04-04 11:22:00 +05:30
|
|
|
GlModalDirective,
|
|
|
|
} from '@gitlab/ui';
|
2021-03-11 19:13:27 +05:30
|
|
|
import $ from 'jquery';
|
2022-06-21 17:19:12 +05:30
|
|
|
import Vue from 'vue';
|
|
|
|
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
|
|
|
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
|
2021-09-04 01:27:46 +05:30
|
|
|
import createFlash from '~/flash';
|
2022-06-21 17:19:12 +05:30
|
|
|
import { isPositiveInteger } from '~/lib/utils/number_utils';
|
|
|
|
import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
|
|
|
|
import { __, s__, sprintf } from '~/locale';
|
2022-01-26 12:08:38 +05:30
|
|
|
import TaskList from '~/task_list';
|
2022-05-07 20:08:51 +05:30
|
|
|
import Tracking from '~/tracking';
|
2022-04-04 11:22:00 +05:30
|
|
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
2022-05-07 20:08:51 +05:30
|
|
|
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
|
2022-04-04 11:22:00 +05:30
|
|
|
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
|
2021-03-11 19:13:27 +05:30
|
|
|
import animateMixin from '../mixins/animate';
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
Vue.use(GlToast);
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
export default {
|
2020-11-24 15:15:51 +05:30
|
|
|
directives: {
|
|
|
|
SafeHtml,
|
2022-04-04 11:22:00 +05:30
|
|
|
GlModal: GlModalDirective,
|
2020-11-24 15:15:51 +05:30
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
components: {
|
|
|
|
GlModal,
|
|
|
|
CreateWorkItem,
|
2022-06-21 17:19:12 +05:30
|
|
|
GlTooltip,
|
2022-05-07 20:08:51 +05:30
|
|
|
WorkItemDetailModal,
|
2022-04-04 11:22:00 +05:30
|
|
|
},
|
2022-05-07 20:08:51 +05:30
|
|
|
mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()],
|
2018-12-13 13:39:08 +05:30
|
|
|
props: {
|
|
|
|
canUpdate: {
|
|
|
|
type: Boolean,
|
|
|
|
required: true,
|
2017-09-10 17:25:29 +05:30
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
descriptionHtml: {
|
|
|
|
type: String,
|
|
|
|
required: true,
|
2017-09-10 17:25:29 +05:30
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
descriptionText: {
|
|
|
|
type: String,
|
2020-11-24 15:15:51 +05:30
|
|
|
required: false,
|
|
|
|
default: '',
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
taskStatus: {
|
|
|
|
type: String,
|
|
|
|
required: false,
|
|
|
|
default: '',
|
|
|
|
},
|
|
|
|
issuableType: {
|
|
|
|
type: String,
|
|
|
|
required: false,
|
|
|
|
default: 'issue',
|
|
|
|
},
|
|
|
|
updateUrl: {
|
|
|
|
type: String,
|
|
|
|
required: false,
|
|
|
|
default: null,
|
|
|
|
},
|
2019-03-02 22:35:43 +05:30
|
|
|
lockVersion: {
|
|
|
|
type: Number,
|
|
|
|
required: false,
|
|
|
|
default: 0,
|
|
|
|
},
|
2022-06-21 17:19:12 +05:30
|
|
|
issueId: {
|
|
|
|
type: Number,
|
|
|
|
required: false,
|
|
|
|
default: null,
|
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
data() {
|
2022-06-21 17:19:12 +05:30
|
|
|
const workItemId = getParameterByName('work_item_id');
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
return {
|
|
|
|
preAnimation: false,
|
|
|
|
pulseAnimation: false,
|
2020-11-24 15:15:51 +05:30
|
|
|
initialUpdate: true,
|
2022-04-04 11:22:00 +05:30
|
|
|
taskButtons: [],
|
|
|
|
activeTask: {},
|
2022-06-21 17:19:12 +05:30
|
|
|
workItemId: isPositiveInteger(workItemId)
|
|
|
|
? convertToGraphQLId(TYPE_WORK_ITEM, workItemId)
|
|
|
|
: undefined,
|
2018-12-13 13:39:08 +05:30
|
|
|
};
|
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
computed: {
|
2022-05-07 20:08:51 +05:30
|
|
|
showWorkItemDetailModal() {
|
|
|
|
return Boolean(this.workItemId);
|
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
workItemsEnabled() {
|
|
|
|
return this.glFeatures.workItems;
|
|
|
|
},
|
2022-06-21 17:19:12 +05:30
|
|
|
issueGid() {
|
|
|
|
return this.issueId ? convertToGraphQLId(TYPE_WORK_ITEM, this.issueId) : null;
|
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
watch: {
|
2020-11-24 15:15:51 +05:30
|
|
|
descriptionHtml(newDescription, oldDescription) {
|
|
|
|
if (!this.initialUpdate && newDescription !== oldDescription) {
|
|
|
|
this.animateChange();
|
|
|
|
} else {
|
|
|
|
this.initialUpdate = false;
|
|
|
|
}
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
this.$nextTick(() => {
|
|
|
|
this.renderGFM();
|
2022-06-21 17:19:12 +05:30
|
|
|
if (this.workItemsEnabled) {
|
|
|
|
this.renderTaskActions();
|
|
|
|
}
|
2018-12-13 13:39:08 +05:30
|
|
|
});
|
2017-09-10 17:25:29 +05:30
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
taskStatus() {
|
2018-03-17 18:26:18 +05:30
|
|
|
this.updateTaskStatusText();
|
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
this.renderGFM();
|
|
|
|
this.updateTaskStatusText();
|
2022-04-04 11:22:00 +05:30
|
|
|
|
|
|
|
if (this.workItemsEnabled) {
|
|
|
|
this.renderTaskActions();
|
|
|
|
}
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
renderGFM() {
|
|
|
|
$(this.$refs['gfm-content']).renderGFM();
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
if (this.canUpdate) {
|
|
|
|
// eslint-disable-next-line no-new
|
|
|
|
new TaskList({
|
|
|
|
dataType: this.issuableType,
|
|
|
|
fieldName: 'description',
|
2019-03-02 22:35:43 +05:30
|
|
|
lockVersion: this.lockVersion,
|
2018-12-13 13:39:08 +05:30
|
|
|
selector: '.detail-page-description',
|
2021-12-11 22:18:48 +05:30
|
|
|
onUpdate: this.taskListUpdateStarted.bind(this),
|
|
|
|
onSuccess: this.taskListUpdateSuccess.bind(this),
|
2019-03-02 22:35:43 +05:30
|
|
|
onError: this.taskListUpdateError.bind(this),
|
2018-12-13 13:39:08 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
taskListUpdateStarted() {
|
|
|
|
this.$emit('taskListUpdateStarted');
|
|
|
|
},
|
|
|
|
|
|
|
|
taskListUpdateSuccess() {
|
|
|
|
this.$emit('taskListUpdateSucceeded');
|
|
|
|
},
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
taskListUpdateError() {
|
2021-09-04 01:27:46 +05:30
|
|
|
createFlash({
|
|
|
|
message: sprintf(
|
2021-12-11 22:18:48 +05:30
|
|
|
__(
|
2019-03-02 22:35:43 +05:30
|
|
|
'Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again.',
|
|
|
|
),
|
|
|
|
{
|
|
|
|
issueType: this.issuableType,
|
|
|
|
},
|
|
|
|
),
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
2019-03-02 22:35:43 +05:30
|
|
|
|
|
|
|
this.$emit('taskListUpdateFailed');
|
|
|
|
},
|
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
updateTaskStatusText() {
|
|
|
|
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
|
|
|
|
const $issuableHeader = $('.issuable-meta');
|
|
|
|
const $tasks = $('#task_status', $issuableHeader);
|
|
|
|
const $tasksShort = $('#task_status_short', $issuableHeader);
|
2017-09-10 17:25:29 +05:30
|
|
|
|
2018-12-13 13:39:08 +05:30
|
|
|
if (taskRegexMatches) {
|
|
|
|
$tasks.text(this.taskStatus);
|
|
|
|
$tasksShort.text(
|
|
|
|
`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$tasks.text('');
|
|
|
|
$tasksShort.text('');
|
|
|
|
}
|
2017-09-10 17:25:29 +05:30
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
renderTaskActions() {
|
|
|
|
if (!this.$el?.querySelectorAll) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-21 17:19:12 +05:30
|
|
|
this.taskButtons = [];
|
2022-04-04 11:22:00 +05:30
|
|
|
const taskListFields = this.$el.querySelectorAll('.task-list-item');
|
|
|
|
|
|
|
|
taskListFields.forEach((item, index) => {
|
2022-06-21 17:19:12 +05:30
|
|
|
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;
|
|
|
|
}
|
2022-04-04 11:22:00 +05:30
|
|
|
const button = document.createElement('button');
|
|
|
|
button.classList.add(
|
|
|
|
'btn',
|
|
|
|
'btn-default',
|
|
|
|
'btn-md',
|
|
|
|
'gl-button',
|
|
|
|
'btn-default-tertiary',
|
|
|
|
'gl-left-0',
|
|
|
|
'gl-p-0!',
|
|
|
|
'gl-top-2',
|
|
|
|
'gl-absolute',
|
|
|
|
'js-add-task',
|
|
|
|
);
|
|
|
|
button.id = `js-task-button-${index}`;
|
|
|
|
this.taskButtons.push(button.id);
|
|
|
|
button.innerHTML = `
|
|
|
|
<svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14">
|
2022-06-21 17:19:12 +05:30
|
|
|
<use href="${gon.sprite_icons}#doc-new"></use>
|
2022-04-04 11:22:00 +05:30
|
|
|
</svg>
|
|
|
|
`;
|
2022-06-21 17:19:12 +05:30
|
|
|
button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
|
|
|
|
button.addEventListener('click', () => this.openCreateTaskModal(button.id));
|
2022-04-04 11:22:00 +05:30
|
|
|
item.prepend(button);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
openCreateTaskModal(id) {
|
2022-06-21 17:19:12 +05:30
|
|
|
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],
|
|
|
|
};
|
2022-04-04 11:22:00 +05:30
|
|
|
this.$refs.modal.show();
|
|
|
|
},
|
|
|
|
closeCreateTaskModal() {
|
|
|
|
this.$refs.modal.hide();
|
|
|
|
},
|
2022-05-07 20:08:51 +05:30
|
|
|
closeWorkItemDetailModal() {
|
2022-06-21 17:19:12 +05:30
|
|
|
this.workItemId = undefined;
|
|
|
|
this.updateWorkItemIdUrlQuery(undefined);
|
2022-05-07 20:08:51 +05:30
|
|
|
},
|
2022-06-21 17:19:12 +05:30
|
|
|
handleCreateTask(description) {
|
|
|
|
this.$emit('updateDescription', description);
|
2022-04-04 11:22:00 +05:30
|
|
|
this.closeCreateTaskModal();
|
|
|
|
},
|
2022-06-21 17:19:12 +05:30
|
|
|
handleDeleteTask() {
|
|
|
|
this.$toast.show(s__('WorkItem|Work item deleted'));
|
2022-05-07 20:08:51 +05:30
|
|
|
},
|
2022-06-21 17:19:12 +05:30
|
|
|
updateWorkItemIdUrlQuery(workItemId) {
|
|
|
|
updateHistory({
|
|
|
|
url: setUrlParams({ work_item_id: workItemId }),
|
|
|
|
replace: true,
|
|
|
|
});
|
2022-04-04 11:22:00 +05:30
|
|
|
},
|
2018-12-13 13:39:08 +05:30
|
|
|
},
|
2022-01-26 12:08:38 +05:30
|
|
|
safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] },
|
2018-12-13 13:39:08 +05:30
|
|
|
};
|
2017-09-10 17:25:29 +05:30
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div
|
|
|
|
v-if="descriptionHtml"
|
|
|
|
:class="{
|
2019-02-15 15:39:39 +05:30
|
|
|
'js-task-list-container': canUpdate,
|
2022-04-04 11:22:00 +05:30
|
|
|
'work-items-enabled': workItemsEnabled,
|
2018-03-17 18:26:18 +05:30
|
|
|
}"
|
2018-11-08 19:23:39 +05:30
|
|
|
class="description"
|
2018-03-17 18:26:18 +05:30
|
|
|
>
|
2017-09-10 17:25:29 +05:30
|
|
|
<div
|
2018-11-08 19:23:39 +05:30
|
|
|
ref="gfm-content"
|
2021-11-11 11:23:49 +05:30
|
|
|
v-safe-html:[$options.safeHtmlConfig]="descriptionHtml"
|
2022-04-04 11:22:00 +05:30
|
|
|
data-testid="gfm-content"
|
2017-09-10 17:25:29 +05:30
|
|
|
:class="{
|
|
|
|
'issue-realtime-pre-pulse': preAnimation,
|
2019-02-15 15:39:39 +05:30
|
|
|
'issue-realtime-trigger-pulse': pulseAnimation,
|
2017-09-10 17:25:29 +05:30
|
|
|
}"
|
2019-07-07 11:18:12 +05:30
|
|
|
class="md"
|
2019-02-15 15:39:39 +05:30
|
|
|
></div>
|
2022-06-21 17:19:12 +05:30
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
<textarea
|
|
|
|
v-if="descriptionText"
|
2022-06-21 17:19:12 +05:30
|
|
|
:value="descriptionText"
|
2018-03-17 18:26:18 +05:30
|
|
|
:data-update-url="updateUrl"
|
2018-11-08 19:23:39 +05:30
|
|
|
class="hidden js-task-list-field"
|
2019-07-31 22:56:46 +05:30
|
|
|
dir="auto"
|
2022-04-04 11:22:00 +05:30
|
|
|
data-testid="textarea"
|
2018-03-17 18:26:18 +05:30
|
|
|
>
|
2017-09-10 17:25:29 +05:30
|
|
|
</textarea>
|
2022-06-21 17:19:12 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
<gl-modal
|
|
|
|
ref="modal"
|
|
|
|
modal-id="create-task-modal"
|
|
|
|
:title="s__('WorkItem|New Task')"
|
|
|
|
hide-footer
|
|
|
|
body-class="gl-p-0!"
|
|
|
|
>
|
|
|
|
<create-work-item
|
2022-06-21 17:19:12 +05:30
|
|
|
is-modal
|
2022-04-04 11:22:00 +05:30
|
|
|
:initial-title="activeTask.title"
|
2022-06-21 17:19:12 +05:30
|
|
|
:issue-gid="issueGid"
|
|
|
|
:lock-version="lockVersion"
|
|
|
|
:line-number-start="activeTask.lineNumberStart"
|
|
|
|
:line-number-end="activeTask.lineNumberEnd"
|
2022-04-04 11:22:00 +05:30
|
|
|
@closeModal="closeCreateTaskModal"
|
|
|
|
@onCreate="handleCreateTask"
|
|
|
|
/>
|
|
|
|
</gl-modal>
|
2022-05-07 20:08:51 +05:30
|
|
|
<work-item-detail-modal
|
2022-06-21 17:19:12 +05:30
|
|
|
:can-update="canUpdate"
|
2022-05-07 20:08:51 +05:30
|
|
|
:visible="showWorkItemDetailModal"
|
|
|
|
:work-item-id="workItemId"
|
2022-06-21 17:19:12 +05:30
|
|
|
@workItemDeleted="handleDeleteTask"
|
2022-05-07 20:08:51 +05:30
|
|
|
@close="closeWorkItemDetailModal"
|
|
|
|
/>
|
2022-04-04 11:22:00 +05:30
|
|
|
<template v-if="workItemsEnabled">
|
2022-06-21 17:19:12 +05:30
|
|
|
<gl-tooltip v-for="item in taskButtons" :key="item" :target="item">
|
|
|
|
{{ s__('WorkItem|Convert to work item') }}
|
|
|
|
</gl-tooltip>
|
2022-04-04 11:22:00 +05:30
|
|
|
</template>
|
2017-09-10 17:25:29 +05:30
|
|
|
</div>
|
|
|
|
</template>
|