diff --git a/packages/editor/src/markdown/markdown-kit.test.ts b/packages/editor/src/markdown/markdown-kit.test.ts index 83f48824..7ea7d394 100644 --- a/packages/editor/src/markdown/markdown-kit.test.ts +++ b/packages/editor/src/markdown/markdown-kit.test.ts @@ -481,6 +481,28 @@ describe("markdown-kit deserialization", () => { expect(imageNode?.height).toBeUndefined() }) + it("deserializes uppercase image embeds into image nodes", async () => { + const editor = createMarkdownEditor() + const value = deserializeMd(editor, "![[assets/PIC.PNG]]") + const imageNode = findNodeByType(value as any[], KEYS.img) + + expect(imageNode).toMatchObject({ + url: "assets/PIC.PNG", + embedTarget: "assets/PIC.PNG", + }) + }) + + it("deserializes heic image embeds into image nodes", async () => { + const editor = createMarkdownEditor() + const value = deserializeMd(editor, "![[assets/photo.heic]]") + const imageNode = findNodeByType(value as any[], KEYS.img) + + expect(imageNode).toMatchObject({ + url: "assets/photo.heic", + embedTarget: "assets/photo.heic", + }) + }) + it("round-trips width-only image embeds", async () => { const editor = createMarkdownEditor() const initialValue = [ diff --git a/packages/editor/src/markdown/markdown-kit.ts b/packages/editor/src/markdown/markdown-kit.ts index dd9a40a0..ea335648 100644 --- a/packages/editor/src/markdown/markdown-kit.ts +++ b/packages/editor/src/markdown/markdown-kit.ts @@ -10,6 +10,7 @@ import { } from "@platejs/markdown" import remarkCallout from "@r4ai/remark-callout" import { phrasing } from "mdast-util-phrasing" +import { extname } from "pathe" import { type Descendant, getPluginType, @@ -41,6 +42,22 @@ import { const EQUATION_ENVIRONMENT_REGEX = /^\\begin\{([^}]+)\}[\r\n]+([\s\S]*?)[\r\n]+\\end\{\1\}\s*$/ +const IMAGE_EMBED_EXTENSIONS = new Set([ + ".apng", + ".avif", + ".bmp", + ".gif", + ".heic", + ".heif", + ".ico", + ".jpeg", + ".jpg", + ".png", + ".svg", + ".tif", + ".tiff", + ".webp", +]) type MdastRoot = { type: "root" @@ -120,6 +137,24 @@ function safelyDecodeUri(url: string): string { } } +function isImageEmbedSource(value: unknown): boolean { + if (typeof value !== "string") { + return false + } + + const decoded = safelyDecodeUri(value.trim()) + if (!decoded) { + return false + } + + const pathPart = decoded.split("#", 1)[0]?.split("?", 1)[0] ?? "" + if (!pathPart) { + return false + } + + return IMAGE_EMBED_EXTENSIONS.has(extname(pathPart).toLowerCase()) +} + function normalizeWikiTarget(url: string): string { let value = safelyDecodeUri(url.trim()) @@ -426,8 +461,12 @@ export const createMarkdownKit = ({ const hProperties = mdastNode.data?.hProperties ?? {} const url = hProperties.src || mdastNode.data?.path || target const { width, height } = getEmbedDimensions(mdastNode) + const shouldDeserializeAsImage = + hName === "img" || + isImageEmbedSource(target) || + isImageEmbedSource(url) - if (hName === "img") { + if (shouldDeserializeAsImage) { const altText = typeof hProperties.alt === "string" ? hProperties.alt : ""