From 51a48283b010d26b5223d57461efa1fd4c61b5fc Mon Sep 17 00:00:00 2001 From: Guillaume Flambard Date: Wed, 2 Sep 2026 11:33:57 +0200 Subject: [PATCH] fix(image): keep data: URI images on markdown round-trip Markdown files can legitimately contain base64 images. Both image parse rules excluded img[src^=data:] because the TipTap allowBase64 option defaults to false, so those images never made it into the document and were silently dropped from the file on the next save (#9108). Enable allowBase64 on the Image and ImageInline extensions: the file content is the user's own, and the editor is expected to preserve it, not to filter it. Regression test covers both the block (figure) and the inline image path. Signed-off-by: Guillaume Flambard --- src/nodes/Image.ts | 4 ++++ src/nodes/ImageInline.ts | 3 +++ src/tests/markdown.spec.js | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/src/nodes/Image.ts b/src/nodes/Image.ts index 405dc92a694..303564640a7 100644 --- a/src/nodes/Image.ts +++ b/src/nodes/Image.ts @@ -54,6 +54,10 @@ const Image = TiptapImage.extend({ return { ...this.parent?.() as ImageOptions, noLazyImages: false, + // Markdown files can legitimately contain base64 data: URI images. + // Parsing them is required, otherwise they are silently dropped on + // the next save (issue #9108). + allowBase64: true, } }, diff --git a/src/nodes/ImageInline.ts b/src/nodes/ImageInline.ts index 75dbeeea00f..168aaabab32 100644 --- a/src/nodes/ImageInline.ts +++ b/src/nodes/ImageInline.ts @@ -49,6 +49,9 @@ const ImageInline = TiptapImage.extend({ ...this.parent?.() as ImageOptions, noLazyImages: false, inline: true, + // See Image.ts: data: URI images must survive an edit round-trip + // instead of being dropped on save (issue #9108). + allowBase64: true, } }, diff --git a/src/tests/markdown.spec.js b/src/tests/markdown.spec.js index 2f5bb9e5011..57015b673c1 100644 --- a/src/tests/markdown.spec.js +++ b/src/tests/markdown.spec.js @@ -38,6 +38,13 @@ describe('Markdown though editor', () => { expect(markdownThroughEditor('~~Test~~')).toBe('~~Test~~') expect(markdownThroughEditor('Have an `inline code` element')).toBe('Have an `inline code` element') }) + test('images with data: URI survive a round-trip (#9108)', ({ markdownThroughEditor }) => { + const dataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==' + // standalone image (block level, wrapped in a figure) + expect(markdownThroughEditor(`![pixel](${dataUri})`)).toBe(`![pixel](${dataUri})`) + // inline image inside a paragraph + expect(markdownThroughEditor(`Before ![pixel](${dataUri}) after`)).toBe(`Before ![pixel](${dataUri}) after`) + }) test('ul', ({ markdownThroughEditor }) => { expect(markdownThroughEditor('+ foo\n+ bar')).toBe('+ foo\n+ bar') expect(markdownThroughEditor('* foo\n* bar')).toBe('* foo\n* bar')