fix(editor): keep editor open when emptying content from the outside (#3852)
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/3852
This commit is contained in:
commit
923fc4eaa0
@ -5,6 +5,19 @@ import {ProjectFactory} from '../../factories/project'
|
|||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {prepareProjects} from './prepareProjects'
|
import {prepareProjects} from './prepareProjects'
|
||||||
|
|
||||||
|
function createSingleTaskInBucket(count = 1, attrs = {}) {
|
||||||
|
const projects = ProjectFactory.create(1)
|
||||||
|
const buckets = BucketFactory.create(2, {
|
||||||
|
project_id: projects[0].id,
|
||||||
|
})
|
||||||
|
const tasks = TaskFactory.create(count, {
|
||||||
|
project_id: projects[0].id,
|
||||||
|
bucket_id: buckets[0].id,
|
||||||
|
...attrs,
|
||||||
|
})
|
||||||
|
return tasks[0]
|
||||||
|
}
|
||||||
|
|
||||||
describe('Project View Kanban', () => {
|
describe('Project View Kanban', () => {
|
||||||
createFakeUserAndLogin()
|
createFakeUserAndLogin()
|
||||||
prepareProjects()
|
prepareProjects()
|
||||||
@ -207,15 +220,7 @@ describe('Project View Kanban', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('Should remove a task from the board when deleting it', () => {
|
it('Should remove a task from the board when deleting it', () => {
|
||||||
const projects = ProjectFactory.create(1)
|
const task = createSingleTaskInBucket(5)
|
||||||
const buckets = BucketFactory.create(2, {
|
|
||||||
project_id: projects[0].id,
|
|
||||||
})
|
|
||||||
const tasks = TaskFactory.create(5, {
|
|
||||||
project_id: 1,
|
|
||||||
bucket_id: buckets[0].id,
|
|
||||||
})
|
|
||||||
const task = tasks[0]
|
|
||||||
cy.visit('/projects/1/kanban')
|
cy.visit('/projects/1/kanban')
|
||||||
|
|
||||||
cy.get('.kanban .bucket .tasks .task')
|
cy.get('.kanban .bucket .tasks .task')
|
||||||
@ -238,4 +243,43 @@ describe('Project View Kanban', () => {
|
|||||||
cy.get('.kanban .bucket .tasks')
|
cy.get('.kanban .bucket .tasks')
|
||||||
.should('not.contain', task.title)
|
.should('not.contain', task.title)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should show a task description icon if the task has a description', () => {
|
||||||
|
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||||
|
const task = createSingleTaskInBucket(1, {
|
||||||
|
description: 'Lorem Ipsum',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||||
|
cy.wait('@loadTasks')
|
||||||
|
|
||||||
|
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not show a task description icon if the task has an empty description', () => {
|
||||||
|
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||||
|
const task = createSingleTaskInBucket(1, {
|
||||||
|
description: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||||
|
cy.wait('@loadTasks')
|
||||||
|
|
||||||
|
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||||
|
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
|
||||||
|
const task = createSingleTaskInBucket(1, {
|
||||||
|
description: '<p></p>',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit(`/projects/${task.project_id}/kanban`)
|
||||||
|
cy.wait('@loadTasks')
|
||||||
|
|
||||||
|
cy.get('.bucket .tasks .task .footer .icon svg')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
})
|
})
|
@ -36,7 +36,7 @@ function uploadAttachmentAndVerify(taskId: number) {
|
|||||||
cy.get('.task-view .action-buttons .button')
|
cy.get('.task-view .action-buttons .button')
|
||||||
.contains('Add Attachments')
|
.contains('Add Attachments')
|
||||||
.click()
|
.click()
|
||||||
cy.get('input[type=file]', {timeout: 1000})
|
cy.get('input[type=file]#files', {timeout: 1000})
|
||||||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
||||||
cy.wait('@uploadAttachment')
|
cy.wait('@uploadAttachment')
|
||||||
|
|
||||||
@ -112,10 +112,50 @@ describe('Task', () => {
|
|||||||
.should('contain', 'Favorites')
|
.should('contain', 'Favorites')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should show a task description icon if the task has a description', () => {
|
||||||
|
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||||
|
TaskFactory.create(1, {
|
||||||
|
description: 'Lorem Ipsum',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit('/projects/1/list')
|
||||||
|
cy.wait('@loadTasks')
|
||||||
|
|
||||||
|
cy.get('.tasks .task .project-task-icon')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not show a task description icon if the task has an empty description', () => {
|
||||||
|
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||||
|
TaskFactory.create(1, {
|
||||||
|
description: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit('/projects/1/list')
|
||||||
|
cy.wait('@loadTasks')
|
||||||
|
|
||||||
|
cy.get('.tasks .task .project-task-icon')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||||
|
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||||
|
TaskFactory.create(1, {
|
||||||
|
description: '<p></p>',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit('/projects/1/list')
|
||||||
|
cy.wait('@loadTasks')
|
||||||
|
|
||||||
|
cy.get('.tasks .task .project-task-icon')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
describe('Task Detail View', () => {
|
describe('Task Detail View', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TaskCommentFactory.truncate()
|
TaskCommentFactory.truncate()
|
||||||
LabelTaskFactory.truncate()
|
LabelTaskFactory.truncate()
|
||||||
|
TaskAttachmentFactory.truncate()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Shows all task details', () => {
|
it('Shows all task details', () => {
|
||||||
@ -213,6 +253,45 @@ describe('Task', () => {
|
|||||||
.should('exist')
|
.should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Shows an empty editor when the description of a task is empty', () => {
|
||||||
|
const tasks = TaskFactory.create(1, {
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
})
|
||||||
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
|
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||||
|
.should('have.attr', 'data-placeholder')
|
||||||
|
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||||
|
.should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows a preview editor when the description of a task is not empty', () => {
|
||||||
|
const tasks = TaskFactory.create(1, {
|
||||||
|
id: 1,
|
||||||
|
description: 'Lorem Ipsum dolor sit amet',
|
||||||
|
})
|
||||||
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
|
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||||
|
.should('not.have.attr', 'data-placeholder')
|
||||||
|
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Shows a preview editor when the description of a task contains html', () => {
|
||||||
|
const tasks = TaskFactory.create(1, {
|
||||||
|
id: 1,
|
||||||
|
description: '<p>Lorem Ipsum dolor sit amet</p>',
|
||||||
|
})
|
||||||
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
|
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||||
|
.should('not.have.attr', 'data-placeholder')
|
||||||
|
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||||
|
.should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
it('Can add a new comment', () => {
|
it('Can add a new comment', () => {
|
||||||
const tasks = TaskFactory.create(1, {
|
const tasks = TaskFactory.create(1, {
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -692,7 +771,7 @@ describe('Task', () => {
|
|||||||
.should('exist')
|
.should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can check items off a checklist', () => {
|
it.only('Can check items off a checklist', () => {
|
||||||
const tasks = TaskFactory.create(1, {
|
const tasks = TaskFactory.create(1, {
|
||||||
id: 1,
|
id: 1,
|
||||||
description: `
|
description: `
|
||||||
@ -761,7 +840,7 @@ describe('Task', () => {
|
|||||||
.should('exist')
|
.should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it.only('Should render an image from attachment', async () => {
|
it('Should render an image from attachment', async () => {
|
||||||
|
|
||||||
TaskAttachmentFactory.truncate()
|
TaskAttachmentFactory.truncate()
|
||||||
|
|
||||||
|
@ -118,7 +118,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
|
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
|
||||||
import {refDebounced} from '@vueuse/core'
|
|
||||||
|
|
||||||
import EditorToolbar from './EditorToolbar.vue'
|
import EditorToolbar from './EditorToolbar.vue'
|
||||||
|
|
||||||
@ -173,6 +172,7 @@ import {Placeholder} from '@tiptap/extension-placeholder'
|
|||||||
import {eventToHotkeyString} from '@github/hotkey'
|
import {eventToHotkeyString} from '@github/hotkey'
|
||||||
import {mergeAttributes} from '@tiptap/core'
|
import {mergeAttributes} from '@tiptap/core'
|
||||||
import {createRandomID} from '@/helpers/randomId'
|
import {createRandomID} from '@/helpers/randomId'
|
||||||
|
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||||
|
|
||||||
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
|
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
@ -200,7 +200,9 @@ const CustomTableCell = TableCell.extend({
|
|||||||
})
|
})
|
||||||
|
|
||||||
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
|
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
|
||||||
const loadedAttachments = ref<{ [key: CacheKey]: string }>({})
|
const loadedAttachments = ref<{
|
||||||
|
[key: CacheKey]: string
|
||||||
|
}>({})
|
||||||
|
|
||||||
const CustomImage = Image.extend({
|
const CustomImage = Image.extend({
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
@ -272,7 +274,6 @@ const {
|
|||||||
showSave = false,
|
showSave = false,
|
||||||
placeholder = '',
|
placeholder = '',
|
||||||
editShortcut = '',
|
editShortcut = '',
|
||||||
initialMode = 'edit',
|
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
modelValue: string,
|
modelValue: string,
|
||||||
uploadCallback?: UploadCallback,
|
uploadCallback?: UploadCallback,
|
||||||
@ -281,13 +282,11 @@ const {
|
|||||||
showSave?: boolean,
|
showSave?: boolean,
|
||||||
placeholder?: string,
|
placeholder?: string,
|
||||||
editShortcut?: string,
|
editShortcut?: string,
|
||||||
initialMode?: Mode,
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'save'])
|
const emit = defineEmits(['update:modelValue', 'save'])
|
||||||
|
|
||||||
const inputHTML = ref('')
|
const internalMode = ref<Mode>('edit')
|
||||||
const internalMode = ref<Mode>(initialMode)
|
|
||||||
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
|
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
@ -359,14 +358,28 @@ const editor = useEditor({
|
|||||||
TaskItem.configure({
|
TaskItem.configure({
|
||||||
nested: true,
|
nested: true,
|
||||||
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
|
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
|
||||||
if (isEditEnabled) {
|
if (!isEditEnabled) {
|
||||||
node.attrs.checked = checked
|
return false
|
||||||
inputHTML.value = editor.value?.getHTML()
|
|
||||||
bubbleSave()
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// The following is a workaround for this bug:
|
||||||
|
// https://github.com/ueberdosis/tiptap/issues/4521
|
||||||
|
// https://github.com/ueberdosis/tiptap/issues/3676
|
||||||
|
|
||||||
|
editor.value!.state.doc.descendants((subnode, pos) => {
|
||||||
|
if (node.eq(subnode)) {
|
||||||
|
const {tr} = editor.value!.state
|
||||||
|
tr.setNodeMarkup(pos, undefined, {
|
||||||
|
...node.attrs,
|
||||||
|
checked,
|
||||||
|
})
|
||||||
|
editor.value!.view.dispatch(tr)
|
||||||
|
bubbleSave()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -376,52 +389,55 @@ const editor = useEditor({
|
|||||||
BubbleMenu,
|
BubbleMenu,
|
||||||
],
|
],
|
||||||
onUpdate: () => {
|
onUpdate: () => {
|
||||||
inputHTML.value = editor.value!.getHTML()
|
bubbleNow()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => modelValue,
|
|
||||||
value => {
|
|
||||||
inputHTML.value = value
|
|
||||||
|
|
||||||
if (!editor?.value) return
|
|
||||||
|
|
||||||
if (editor.value.getHTML() === value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.value.commands.setContent(value, false)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const debouncedInputHTML = refDebounced(inputHTML, 1000)
|
|
||||||
watch(debouncedInputHTML, () => bubbleNow())
|
|
||||||
|
|
||||||
function bubbleNow() {
|
|
||||||
emit('update:modelValue', inputHTML.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function bubbleSave() {
|
|
||||||
bubbleNow()
|
|
||||||
emit('save', inputHTML.value)
|
|
||||||
if (isEditing.value) {
|
|
||||||
internalMode.value = 'preview'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEdit() {
|
|
||||||
internalMode.value = 'edit'
|
|
||||||
editor.value?.commands.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => isEditing.value,
|
() => isEditing.value,
|
||||||
() => {
|
() => {
|
||||||
editor.value?.setEditable(isEditing.value)
|
editor.value?.setEditable(isEditing.value)
|
||||||
},
|
},
|
||||||
|
{immediate: true},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelValue,
|
||||||
|
value => {
|
||||||
|
if (!editor?.value) return
|
||||||
|
|
||||||
|
if (editor.value.getHTML() === value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setModeAndValue(value)
|
||||||
|
},
|
||||||
|
{immediate: true},
|
||||||
|
)
|
||||||
|
|
||||||
|
function bubbleNow() {
|
||||||
|
if (editor.value?.getHTML() === modelValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:modelValue', editor.value?.getHTML())
|
||||||
|
}
|
||||||
|
|
||||||
|
function bubbleSave() {
|
||||||
|
bubbleNow()
|
||||||
|
emit('save', editor.value?.getHTML())
|
||||||
|
if (isEditing.value) {
|
||||||
|
internalMode.value = 'preview'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEdit(focus: boolean = true) {
|
||||||
|
internalMode.value = 'edit'
|
||||||
|
if (focus) {
|
||||||
|
editor.value?.commands.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => editor.value?.destroy())
|
onBeforeUnmount(() => editor.value?.destroy())
|
||||||
|
|
||||||
const uploadInputRef = ref<HTMLInputElement | null>(null)
|
const uploadInputRef = ref<HTMLInputElement | null>(null)
|
||||||
@ -491,15 +507,17 @@ function setLink() {
|
|||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
internalMode.value = initialMode
|
|
||||||
nextTick(() => {
|
|
||||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
|
||||||
input?.addEventListener('paste', handleImagePaste)
|
|
||||||
})
|
|
||||||
if (editShortcut !== '') {
|
if (editShortcut !== '') {
|
||||||
document.addEventListener('keydown', setFocusToEditor)
|
document.addEventListener('keydown', setFocusToEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||||
|
input?.addEventListener('paste', handleImagePaste)
|
||||||
|
|
||||||
|
setModeAndValue(modelValue)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@ -512,6 +530,11 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function setModeAndValue(value: string) {
|
||||||
|
internalMode.value = isEditorContentEmpty(value) ? 'edit' : 'preview'
|
||||||
|
editor.value?.commands.setContent(value, false)
|
||||||
|
}
|
||||||
|
|
||||||
function handleImagePaste(event) {
|
function handleImagePaste(event) {
|
||||||
if (event?.clipboardData?.items?.length === 0) {
|
if (event?.clipboardData?.items?.length === 0) {
|
||||||
return
|
return
|
||||||
@ -521,7 +544,6 @@ function handleImagePaste(event) {
|
|||||||
|
|
||||||
const image = event.clipboardData.items[0]
|
const image = event.clipboardData.items[0]
|
||||||
if (image.kind === 'file' && image.type.startsWith('image/')) {
|
if (image.kind === 'file' && image.type.startsWith('image/')) {
|
||||||
console.log('img', image.getAsFile())
|
|
||||||
uploadAndInsertFiles([image.getAsFile()])
|
uploadAndInsertFiles([image.getAsFile()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,7 +560,7 @@ function setFocusToEditor(event) {
|
|||||||
}
|
}
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
if (initialMode === 'preview' && isEditEnabled && !isEditing.value) {
|
if (!isEditing.value && isEditEnabled) {
|
||||||
internalMode.value = 'edit'
|
internalMode.value = 'edit'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
v-model="description"
|
v-model="description"
|
||||||
@update:model-value="saveWithDelay"
|
@update:model-value="saveWithDelay"
|
||||||
@save="save"
|
@save="save"
|
||||||
:initial-mode="isEditorContentEmpty(description) ? 'edit' : 'preview'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -39,7 +38,6 @@ import Editor from '@/components/input/AsyncEditor'
|
|||||||
|
|
||||||
import type {ITask} from '@/modelTypes/ITask'
|
import type {ITask} from '@/modelTypes/ITask'
|
||||||
import {useTaskStore} from '@/stores/tasks'
|
import {useTaskStore} from '@/stores/tasks'
|
||||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
|
||||||
|
|
||||||
type AttachmentUploadFunction = (file: File, onSuccess: (attachmentUrl: string) => void) => Promise<string>
|
type AttachmentUploadFunction = (file: File, onSuccess: (attachmentUrl: string) => void) => Promise<string>
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<span class="icon" v-if="task.attachments.length > 0">
|
<span class="icon" v-if="task.attachments.length > 0">
|
||||||
<icon icon="paperclip"/>
|
<icon icon="paperclip"/>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="task.description" class="icon">
|
<span v-if="!isEditorContentEmpty(task.description)" class="icon">
|
||||||
<icon icon="align-left"/>
|
<icon icon="align-left"/>
|
||||||
</span>
|
</span>
|
||||||
<span class="icon" v-if="task.repeatAfter.amount > 0">
|
<span class="icon" v-if="task.repeatAfter.amount > 0">
|
||||||
@ -91,6 +91,7 @@ import {useTaskStore} from '@/stores/tasks'
|
|||||||
import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
||||||
import {useAuthStore} from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
import {playPopSound} from '@/helpers/playPop'
|
import {playPopSound} from '@/helpers/playPop'
|
||||||
|
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
<span class="project-task-icon" v-if="task.attachments.length > 0">
|
<span class="project-task-icon" v-if="task.attachments.length > 0">
|
||||||
<icon icon="paperclip"/>
|
<icon icon="paperclip"/>
|
||||||
</span>
|
</span>
|
||||||
<span class="project-task-icon" v-if="task.description">
|
<span class="project-task-icon" v-if="!isEditorContentEmpty(task.description)">
|
||||||
<icon icon="align-left"/>
|
<icon icon="align-left"/>
|
||||||
</span>
|
</span>
|
||||||
<span class="project-task-icon" v-if="task.repeatAfter.amount > 0">
|
<span class="project-task-icon" v-if="task.repeatAfter.amount > 0">
|
||||||
@ -184,6 +184,7 @@ import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
|||||||
import {useIntervalFn} from '@vueuse/core'
|
import {useIntervalFn} from '@vueuse/core'
|
||||||
import {playPopSound} from '@/helpers/playPop'
|
import {playPopSound} from '@/helpers/playPop'
|
||||||
import {useAuthStore} from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
|
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
theTask,
|
theTask,
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
'is-modal': isModal,
|
'is-modal': isModal,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="task-view">
|
<!-- Removing everything until the task is loaded to prevent empty initialization of other components -->
|
||||||
|
<div class="task-view" v-if="visible">
|
||||||
<Heading
|
<Heading
|
||||||
:task="task"
|
:task="task"
|
||||||
@update:task="Object.assign(task, $event)"
|
@update:task="Object.assign(task, $event)"
|
||||||
@ -605,7 +606,8 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object.assign(task.value, await taskService.get({id}))
|
const loaded = await taskService.get({id})
|
||||||
|
Object.assign(task.value, loaded)
|
||||||
attachmentStore.set(task.value.attachments)
|
attachmentStore.set(task.value.attachments)
|
||||||
taskColor.value = task.value.hexColor
|
taskColor.value = task.value.hexColor
|
||||||
setActiveFields()
|
setActiveFields()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user