feat(editor): only load attachment images when rendering is done

This commit is contained in:
kolaente 2023-10-22 12:38:34 +02:00
parent d7503dc4a2
commit f6d5cbcf6f
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B

View File

@ -178,6 +178,8 @@ import XButton from '@/components/input/button.vue'
import {Placeholder} from '@tiptap/extension-placeholder' import {Placeholder} from '@tiptap/extension-placeholder'
import {eventToHotkeyString} from '@github/hotkey' import {eventToHotkeyString} from '@github/hotkey'
import {useBaseStore} from '@/stores/base' import {useBaseStore} from '@/stores/base'
import {mergeAttributes} from '@tiptap/core'
import {createRandomID} from '@/helpers/randomId'
const {t} = useI18n() const {t} = useI18n()
@ -202,6 +204,51 @@ const CustomTableCell = TableCell.extend({
}, },
}) })
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
const loadedAttachments = ref<{ [key: CacheKey]: string }>({})
const CustomImage = Image.extend({
renderHTML({HTMLAttributes}) {
if (HTMLAttributes.src?.startsWith(window.API_URL)) {
const id = 'tiptap-image-' + createRandomID()
nextTick(async () => {
const img = document.getElementById(id)
if (!img) return
// The url is something like /tasks/<id>/attachments/<id>
const parts = img.dataset?.src.slice(window.API_URL.length + 1).split('/')
const taskId = Number(parts[1])
const attachmentId = Number(parts[3])
const cacheKey: CacheKey = `${taskId}-${attachmentId}`
if (typeof loadedAttachments.value[cacheKey] === 'undefined') {
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
const attachmentService = new AttachmentService()
const url = await attachmentService.getBlobUrl(attachment)
loadedAttachments.value[cacheKey] = url
}
img.src = loadedAttachments.value[cacheKey]
})
return ['img', mergeAttributes(this.options.HTMLAttributes, {
'data-src': HTMLAttributes.src,
src: '#',
alt: HTMLAttributes.alt,
title: HTMLAttributes.title,
id,
})]
}
return ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]
},
})
type Mode = 'edit' | 'preview' type Mode = 'edit' | 'preview'
const { const {
@ -240,12 +287,10 @@ watch(
if (!modelValue.startsWith(TIPTAP_TEXT_VALUE_PREFIX)) { if (!modelValue.startsWith(TIPTAP_TEXT_VALUE_PREFIX)) {
// convert Markdown to HTML // convert Markdown to HTML
inputHTML.value = TIPTAP_TEXT_VALUE_PREFIX + marked.parse(modelValue) inputHTML.value = TIPTAP_TEXT_VALUE_PREFIX + marked.parse(modelValue)
nextTick(() => loadImages())
return return
} }
inputHTML.value = modelValue.replace(tiptapRegex, '') inputHTML.value = modelValue.replace(tiptapRegex, '')
nextTick(() => loadImages())
}, },
{immediate: true}, {immediate: true},
) )
@ -259,43 +304,6 @@ function setEdit() {
editor.value?.commands.focus() editor.value?.commands.focus()
} }
function onImageAdded() {
bubbleSave()
loadImages()
}
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
const loadedAttachments = ref<{ [key: CacheKey]: string }>({})
function loadImages() {
const attachmentImage = document.querySelectorAll<HTMLImageElement>('.tiptap__editor img')
const attachmentService = new AttachmentService()
if (attachmentImage) {
Array.from(attachmentImage).forEach(async (img) => {
if (!img.src.startsWith(window.API_URL)) {
return
}
// The url is something like /tasks/<id>/attachments/<id>
const parts = img.src.slice(window.API_URL.length + 1).split('/')
const taskId = Number(parts[1])
const attachmentId = Number(parts[3])
const cacheKey: CacheKey = `${taskId}-${attachmentId}`
if (typeof loadedAttachments.value[cacheKey] !== 'undefined') {
img.src = loadedAttachments.value[cacheKey]
return
}
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
const url = await attachmentService.getBlobUrl(attachment)
img.src = url
loadedAttachments.value[cacheKey] = url
})
}
}
const debouncedInputHTML = refDebounced(inputHTML, 1000) const debouncedInputHTML = refDebounced(inputHTML, 1000)
watch(debouncedInputHTML, () => bubbleNow()) watch(debouncedInputHTML, () => bubbleNow())
@ -375,7 +383,7 @@ const editor = useEditor({
// Custom TableCell with backgroundColor attribute // Custom TableCell with backgroundColor attribute
CustomTableCell, CustomTableCell,
Image, CustomImage,
TaskList, TaskList,
TaskItem.configure({ TaskItem.configure({
@ -406,7 +414,7 @@ watch(
() => isEditing.value, () => isEditing.value,
() => { () => {
editor.value?.setEditable(isEditing.value) editor.value?.setEditable(isEditing.value)
} },
) )
watch(inputHTML, (value) => { watch(inputHTML, (value) => {
@ -437,7 +445,7 @@ function uploadAndInsertFiles(files: File[] | FileList) {
.setImage({src: url}) .setImage({src: url})
.run() .run()
}) })
onImageAdded() bubbleSave()
}) })
} }
@ -459,7 +467,7 @@ function addImage() {
if (url) { if (url) {
editor.value?.chain().focus().setImage({src: url}).run() editor.value?.chain().focus().setImage({src: url}).run()
onImageAdded() bubbleSave()
} }
} }