From c5143c4a0bf6f9f48796273bd5907f10fa0225f2 Mon Sep 17 00:00:00 2001 From: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:11:42 -0400 Subject: [PATCH] fix(mdx-to-markdown): rewrite linked images as images, not links Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- changelog.d/fixed-mdx-linked-image.md | 1 + scripts/mdx-to-markdown.edge-cases.test.ts | 24 ++++++++++++++++++++++ scripts/mdx-to-markdown.ts | 23 +++++++++++---------- 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 changelog.d/fixed-mdx-linked-image.md diff --git a/changelog.d/fixed-mdx-linked-image.md b/changelog.d/fixed-mdx-linked-image.md new file mode 100644 index 0000000..51ade57 --- /dev/null +++ b/changelog.d/fixed-mdx-linked-image.md @@ -0,0 +1 @@ +- MDX-to-Markdown sync now rewrites a linked image (`[![alt](/images/x.png)](/docs/...)`) as an image plus a link, so the asset is self-hosted and the link target points at the sibling site (hivecommons/docs#368). diff --git a/scripts/mdx-to-markdown.edge-cases.test.ts b/scripts/mdx-to-markdown.edge-cases.test.ts index 648d24a..d691b62 100644 --- a/scripts/mdx-to-markdown.edge-cases.test.ts +++ b/scripts/mdx-to-markdown.edge-cases.test.ts @@ -242,3 +242,27 @@ describe("convertMdxToMarkdown — options and frontmatter fallbacks", () => { expect(out.notes).toEqual([]); }); }); + +describe("convertMdxToMarkdown — linked images", () => { + it("rewrites a linked image as an image and the enclosing link as a link", () => { + const r = convertMdxToMarkdown( + "---\ntitle: T\n---\n\n[![Badge](/images/badge.png)](/docs/guide)\n", + { siteBase: "https://example.dev", rewriteImage: s => `RAW${s}` } + ); + expect(r.images).toEqual(["/images/badge.png"]); + expect(r.markdown).toContain( + "[![Badge](RAW/images/badge.png)](https://example.dev/docs/guide)" + ); + }); + + it("still rewrites a plain image and a plain link independently", () => { + const r = convertMdxToMarkdown( + "See [guide](/docs/guide) and ![solo](/images/solo.png)\n", + { siteBase: "https://example.dev", rewriteImage: s => `RAW${s}` } + ); + expect(r.images).toEqual(["/images/solo.png"]); + expect(r.markdown).toContain( + "See [guide](https://example.dev/docs/guide) and ![solo](RAW/images/solo.png)" + ); + }); +}); diff --git a/scripts/mdx-to-markdown.ts b/scripts/mdx-to-markdown.ts index d2f4b2a..fa3dfd7 100644 --- a/scripts/mdx-to-markdown.ts +++ b/scripts/mdx-to-markdown.ts @@ -314,20 +314,21 @@ export function convertMdxToMarkdown( notes.push(`removed inline tag ${m.trim()} (<${name}>)`); return ""; }); - // Links and images. + // Images first so a linked image is rewritten before the enclosing link. s = s.replace( - /(!?)\[([^\]]*)\]\(([^)\s]+)((?:\s+"[^"]*")?)\)/g, - (m, bang, alt, href, tail) => { - if (bang === "!" && href.startsWith(imagePrefix)) { - images.push(href); - return `![${alt}](${rewriteImage(href)}${tail})`; - } - if (bang !== "!" && href.startsWith("/")) { - return `[${alt}](${siteBase}${href}${tail})`; - } - return m; + /!\[([^\]]*)\]\(([^)\s]+)((?:\s+"[^"]*")?)\)/g, + (m, alt, href, tail) => { + if (!href.startsWith(imagePrefix)) return m; + images.push(href); + return `![${alt}](${rewriteImage(href)}${tail})`; } ); + // Links; the text may be an already-rewritten image. + s = s.replace( + /(? + href.startsWith("/") ? `[${text}](${siteBase}${href}${tail})` : m + ); // Stray braces would be parsed as MDX expressions. s = s.replace(/[{}]/g, c => (c === "{" ? "{" : "}")); return s;