diff options
Diffstat (limited to 'app/assets/javascripts/issues/show/components/description.vue')
-rw-r--r-- | app/assets/javascripts/issues/show/components/description.vue | 126 |
1 files changed, 121 insertions, 5 deletions
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 7be4c13f544..eeccf886b65 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -1,18 +1,31 @@ <script> -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { + GlSafeHtmlDirective as SafeHtml, + GlModal, + GlModalDirective, + GlPopover, + GlButton, +} from '@gitlab/ui'; import $ from 'jquery'; import createFlash from '~/flash'; import { __, sprintf } from '~/locale'; import TaskList from '~/task_list'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; import animateMixin from '../mixins/animate'; export default { directives: { SafeHtml, + GlModal: GlModalDirective, }, - - mixins: [animateMixin], - + components: { + GlModal, + GlPopover, + CreateWorkItem, + GlButton, + }, + mixins: [animateMixin, glFeatureFlagMixin()], props: { canUpdate: { type: Boolean, @@ -53,8 +66,15 @@ export default { preAnimation: false, pulseAnimation: false, initialUpdate: true, + taskButtons: [], + activeTask: {}, }; }, + computed: { + workItemsEnabled() { + return this.glFeatures.workItems; + }, + }, watch: { descriptionHtml(newDescription, oldDescription) { if (!this.initialUpdate && newDescription !== oldDescription) { @@ -74,6 +94,10 @@ export default { mounted() { this.renderGFM(); this.updateTaskStatusText(); + + if (this.workItemsEnabled) { + this.renderTaskActions(); + } }, methods: { renderGFM() { @@ -132,6 +156,63 @@ export default { $tasksShort.text(''); } }, + renderTaskActions() { + if (!this.$el?.querySelectorAll) { + return; + } + + const taskListFields = this.$el.querySelectorAll('.task-list-item'); + + taskListFields.forEach((item, index) => { + 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"> + <use href="${gon.sprite_icons}#ellipsis_v"></use> + </svg> + `; + item.prepend(button); + }); + }, + openCreateTaskModal(id) { + this.activeTask = { id, title: this.$el.querySelector(`#${id}`).parentElement.innerText }; + this.$refs.modal.show(); + }, + closeCreateTaskModal() { + this.$refs.modal.hide(); + }, + handleCreateTask(title) { + 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> + <a href="#">${title}</a> + `; + listItem.insertBefore(taskBadge, listItem.lastChild); + listItem.removeChild(listItem.lastChild); + this.closeCreateTaskModal(); + }, + focusButton() { + this.$refs.convertButton[0].$el.focus(); + }, }, safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] }, }; @@ -142,12 +223,14 @@ export default { v-if="descriptionHtml" :class="{ 'js-task-list-container': canUpdate, + 'work-items-enabled': workItemsEnabled, }" class="description" > <div ref="gfm-content" v-safe-html:[$options.safeHtmlConfig]="descriptionHtml" + data-testid="gfm-content" :class="{ 'issue-realtime-pre-pulse': preAnimation, 'issue-realtime-trigger-pulse': pulseAnimation, @@ -157,13 +240,46 @@ export default { <!-- eslint-disable vue/no-mutating-props --> <textarea v-if="descriptionText" - ref="textarea" v-model="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" + :title="s__('WorkItem|New Task')" + hide-footer + body-class="gl-p-0!" + > + <create-work-item + :is-modal="true" + :initial-title="activeTask.title" + @closeModal="closeCreateTaskModal" + @onCreate="handleCreateTask" + /> + </gl-modal> + <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> + </template> </div> </template> |