From f6d5cbcf6f83a80236caf54cf9060641b7504220 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 22 Oct 2023 12:38:34 +0200 Subject: [PATCH] feat(editor): only load attachment images when rendering is done --- src/components/input/editor/TipTap.vue | 94 ++++++++++++++------------ 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/src/components/input/editor/TipTap.vue b/src/components/input/editor/TipTap.vue index 01ee133d0..afd23d312 100644 --- a/src/components/input/editor/TipTap.vue +++ b/src/components/input/editor/TipTap.vue @@ -178,6 +178,8 @@ import XButton from '@/components/input/button.vue' import {Placeholder} from '@tiptap/extension-placeholder' import {eventToHotkeyString} from '@github/hotkey' import {useBaseStore} from '@/stores/base' +import {mergeAttributes} from '@tiptap/core' +import {createRandomID} from '@/helpers/randomId' 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//attachments/ + 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' const { @@ -240,12 +287,10 @@ watch( if (!modelValue.startsWith(TIPTAP_TEXT_VALUE_PREFIX)) { // convert Markdown to HTML inputHTML.value = TIPTAP_TEXT_VALUE_PREFIX + marked.parse(modelValue) - nextTick(() => loadImages()) return } inputHTML.value = modelValue.replace(tiptapRegex, '') - nextTick(() => loadImages()) }, {immediate: true}, ) @@ -259,43 +304,6 @@ function setEdit() { 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('.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//attachments/ - 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) watch(debouncedInputHTML, () => bubbleNow()) @@ -375,7 +383,7 @@ const editor = useEditor({ // Custom TableCell with backgroundColor attribute CustomTableCell, - Image, + CustomImage, TaskList, TaskItem.configure({ @@ -406,7 +414,7 @@ watch( () => isEditing.value, () => { editor.value?.setEditable(isEditing.value) - } + }, ) watch(inputHTML, (value) => { @@ -437,7 +445,7 @@ function uploadAndInsertFiles(files: File[] | FileList) { .setImage({src: url}) .run() }) - onImageAdded() + bubbleSave() }) } @@ -459,7 +467,7 @@ function addImage() { if (url) { editor.value?.chain().focus().setImage({src: url}).run() - onImageAdded() + bubbleSave() } }