diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx index 0cd427692..16c8f7c92 100644 --- a/apps/web/src/components/editor/tiptap.tsx +++ b/apps/web/src/components/editor/tiptap.tsx @@ -398,7 +398,7 @@ function toIEditor(editor: Editor): IEditor { loadImage: (hash, src) => editor.current?.commands.updateImage( { hash }, - { hash, src, preventUpdate: true } + { hash, dataurl: src, preventUpdate: true } ), sendAttachmentProgress: (hash, type, progress) => editor.current?.commands.setAttachmentProgress({ diff --git a/packages/editor/src/extensions/image/component.tsx b/packages/editor/src/extensions/image/component.tsx index 9a6116fa2..166d08240 100644 --- a/packages/editor/src/extensions/image/component.tsx +++ b/packages/editor/src/extensions/image/component.tsx @@ -44,7 +44,8 @@ export function ImageComponent( ) { const { editor, node, selected } = props; const isMobile = useIsMobile(); - const { src, alt, title, width, height, textDirection, hash } = node.attrs; + const { dataurl, src, alt, title, width, height, textDirection, hash } = + node.attrs; const float = isMobile ? false : node.attrs.float; let align = node.attrs.align; @@ -58,9 +59,10 @@ export function ImageComponent( useEffect( () => { (async () => { - if (!src) return; + if (!src && !dataurl) return; try { - if (isDataUrl(src)) setSource(await toBlobURL(src)); + if (dataurl) setSource(await toBlobURL(dataurl)); + else if (isDataUrl(src)) setSource(await toBlobURL(src)); else { const { url, size, blob, type } = await downloadImage( src, @@ -79,7 +81,7 @@ export function ImageComponent( })(); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [src, imageRef, downloadOptions] + [src, dataurl, imageRef, downloadOptions] ); return ( diff --git a/packages/editor/src/extensions/image/image.ts b/packages/editor/src/extensions/image/image.ts index 767141e57..424e1e313 100644 --- a/packages/editor/src/extensions/image/image.ts +++ b/packages/editor/src/extensions/image/image.ts @@ -35,9 +35,29 @@ export interface ImageOptions { HTMLAttributes: Record; } +/** + * We have two attributes that store the source of an image: + * + * 1. `src` + * 2. `dataurl` + * + * `src` is the image's inherent source. This can contain a URL, a base64-dataurl, + * a blob...etc. We should never touch this attribute. This is also where we store + * the data that we want to upload so after we download a pasted image, its base64 + * dataurl goes in this attribute. + * + * `dataurl` should never get added to the final HTML. This attribute is where we + * restore an image's data after loading a note. + * + * The reason we have 2 instead of a single attribute is to avoid unnecessary processing. + * Keeping everything in the `src` attribute requires us to always send the rendered image + * along with everything else. This is pointless because we already have the image's rendered + * data. + */ export type ImageAttributes = Partial & Partial & { src: string; + dataurl?: string; alt?: string; title?: string; textDirection?: TextDirections; @@ -114,7 +134,12 @@ export const ImageNode = Node.create({ hash: getDataAttribute("hash"), filename: getDataAttribute("filename"), type: getDataAttribute("mime"), - size: getDataAttribute("size") + size: getDataAttribute("size"), + + dataurl: { + ...getDataAttribute("dataurl"), + rendered: false + } }; },