Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
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.vue156
1 files changed, 140 insertions, 16 deletions
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 0b7e128c47b..4f97458dcd1 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -7,22 +7,31 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import $ from 'jquery';
+import Sortable from 'sortablejs';
import Vue from 'vue';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
import createFlash from '~/flash';
import { isPositiveInteger } from '~/lib/utils/number_utils';
import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
+import { getSortableDefaultOptions, isDragging } from '~/sortable/utils';
import TaskList from '~/task_list';
import Tracking from '~/tracking';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import animateMixin from '../mixins/animate';
+import { convertDescriptionWithNewSort } from '../utils';
Vue.use(GlToast);
+const workItemTypes = {
+ TASK: 'task',
+};
+
export default {
directives: {
SafeHtml,
@@ -74,6 +83,11 @@ export default {
required: false,
default: null,
},
+ isUpdating: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
const workItemId = getParameterByName('work_item_id');
@@ -89,10 +103,20 @@ export default {
: undefined,
};
},
- computed: {
- showWorkItemDetailModal() {
- return Boolean(this.workItemId);
+ apollo: {
+ workItem: {
+ query: workItemQuery,
+ variables() {
+ return {
+ id: this.workItemId,
+ };
+ },
+ skip() {
+ return !this.workItemId || !this.workItemsEnabled;
+ },
},
+ },
+ computed: {
workItemsEnabled() {
return this.glFeatures.workItems;
},
@@ -126,6 +150,16 @@ export default {
if (this.workItemsEnabled) {
this.renderTaskActions();
}
+
+ if (this.workItemId) {
+ const taskLink = this.$el.querySelector(
+ `.gfm-issue[data-issue="${getIdFromGraphQLId(this.workItemId)}"]`,
+ );
+ this.openWorkItemDetailModal(taskLink);
+ }
+ },
+ beforeDestroy() {
+ this.removeAllPointerEventListeners();
},
methods: {
renderGFM() {
@@ -142,9 +176,67 @@ export default {
onSuccess: this.taskListUpdateSuccess.bind(this),
onError: this.taskListUpdateError.bind(this),
});
+
+ this.renderSortableLists();
}
},
+ renderSortableLists() {
+ this.removeAllPointerEventListeners();
+
+ const lists = document.querySelectorAll('.description ul, .description ol');
+ lists.forEach((list) => {
+ Array.from(list.children).forEach((listItem) => {
+ listItem.prepend(this.createDragIconElement());
+ this.addPointerEventListeners(listItem);
+ });
+ Sortable.create(
+ list,
+ getSortableDefaultOptions({
+ handle: '.drag-icon',
+ onUpdate: (event) => {
+ const description = convertDescriptionWithNewSort(this.descriptionText, event.to);
+ this.$emit('listItemReorder', description);
+ },
+ }),
+ );
+ });
+ },
+ createDragIconElement() {
+ const container = document.createElement('div');
+ container.innerHTML = `<svg class="drag-icon s14 gl-icon gl-cursor-grab gl-visibility-hidden" role="img" aria-hidden="true">
+ <use href="${gon.sprite_icons}#drag-vertical"></use>
+ </svg>`;
+ return container.firstChild;
+ },
+ addPointerEventListeners(listItem) {
+ const pointeroverListener = (event) => {
+ if (isDragging() || this.isUpdating) {
+ return;
+ }
+ event.target.closest('li').querySelector('.drag-icon').style.visibility = 'visible'; // eslint-disable-line no-param-reassign
+ };
+ const pointeroutListener = (event) => {
+ event.target.closest('li').querySelector('.drag-icon').style.visibility = 'hidden'; // eslint-disable-line no-param-reassign
+ };
+
+ // We use pointerover/pointerout instead of CSS so that when we hover over a
+ // list item with children, the drag icons of its children do not become visible.
+ listItem.addEventListener('pointerover', pointeroverListener);
+ listItem.addEventListener('pointerout', pointeroutListener);
+
+ this.pointerEventListeners = this.pointerEventListeners || new Map();
+ this.pointerEventListeners.set(listItem, [
+ { type: 'pointerover', listener: pointeroverListener },
+ { type: 'pointerout', listener: pointeroutListener },
+ ]);
+ },
+ removeAllPointerEventListeners() {
+ this.pointerEventListeners?.forEach((events, listItem) => {
+ events.forEach((event) => listItem.removeEventListener(event.type, event.listener));
+ this.pointerEventListeners.delete(listItem);
+ });
+ },
taskListUpdateStarted() {
this.$emit('taskListUpdateStarted');
},
@@ -195,10 +287,16 @@ export default {
taskListFields.forEach((item, index) => {
const taskLink = item.querySelector('.gfm-issue');
if (taskLink) {
- const { issue, referenceType } = taskLink.dataset;
+ const { issue, referenceType, issueType } = taskLink.dataset;
+ if (issueType !== workItemTypes.TASK) {
+ return;
+ }
+ const workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
+ this.addHoverListeners(taskLink, workItemId);
taskLink.addEventListener('click', (e) => {
e.preventDefault();
- this.workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
+ this.openWorkItemDetailModal(taskLink);
+ this.workItemId = workItemId;
this.updateWorkItemIdUrlQuery(issue);
this.track('viewed_work_item_from_modal', {
category: 'workItems:show',
@@ -215,10 +313,9 @@ export default {
'btn-md',
'gl-button',
'btn-default-tertiary',
- 'gl-left-0',
'gl-p-0!',
- 'gl-top-2',
- 'gl-absolute',
+ 'gl-mt-n1',
+ 'gl-ml-3',
'js-add-task',
);
button.id = `js-task-button-${index}`;
@@ -229,24 +326,46 @@ export default {
</svg>
`;
button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
- button.addEventListener('click', () => this.openCreateTaskModal(button.id));
- item.prepend(button);
+ button.addEventListener('click', () => this.openCreateTaskModal(button));
+ item.append(button);
});
},
- openCreateTaskModal(id) {
- const { parentElement } = this.$el.querySelector(`#${id}`);
+ addHoverListeners(taskLink, id) {
+ let workItemPrefetch;
+ taskLink.addEventListener('mouseover', () => {
+ workItemPrefetch = setTimeout(() => {
+ this.workItemId = id;
+ }, 150);
+ });
+ taskLink.addEventListener('mouseout', () => {
+ if (workItemPrefetch) {
+ clearTimeout(workItemPrefetch);
+ }
+ });
+ },
+ setActiveTask(el) {
+ const { parentElement } = el;
const lineNumbers = parentElement.getAttribute('data-sourcepos').match(/\b\d+(?=:)/g);
this.activeTask = {
- id,
title: parentElement.innerText,
lineNumberStart: lineNumbers[0],
lineNumberEnd: lineNumbers[1],
};
+ },
+ openCreateTaskModal(el) {
+ this.setActiveTask(el);
this.$refs.modal.show();
},
closeCreateTaskModal() {
this.$refs.modal.hide();
},
+ openWorkItemDetailModal(el) {
+ if (!el) {
+ return;
+ }
+ this.setActiveTask(el);
+ this.$refs.detailsModal.show();
+ },
closeWorkItemDetailModal() {
this.workItemId = undefined;
this.updateWorkItemIdUrlQuery(undefined);
@@ -255,7 +374,8 @@ export default {
this.$emit('updateDescription', description);
this.closeCreateTaskModal();
},
- handleDeleteTask() {
+ handleDeleteTask(description) {
+ this.$emit('updateDescription', description);
this.$toast.show(s__('WorkItem|Work item deleted'));
},
updateWorkItemIdUrlQuery(workItemId) {
@@ -318,9 +438,13 @@ export default {
/>
</gl-modal>
<work-item-detail-modal
+ ref="detailsModal"
:can-update="canUpdate"
- :visible="showWorkItemDetailModal"
:work-item-id="workItemId"
+ :issue-gid="issueGid"
+ :lock-version="lockVersion"
+ :line-number-start="activeTask.lineNumberStart"
+ :line-number-end="activeTask.lineNumberEnd"
@workItemDeleted="handleDeleteTask"
@close="closeWorkItemDetailModal"
/>