diff --git a/src/plugins/rehype/external-links.ts b/src/plugins/rehype/external-links.ts index 68039f18f0fc919..8c7d19196333ee0 100644 --- a/src/plugins/rehype/external-links.ts +++ b/src/plugins/rehype/external-links.ts @@ -1,9 +1,22 @@ import rehypeExternalLinks, { type Options } from "rehype-external-links"; +import type { Element } from "hast"; + +function hasImgChild(node: Element): boolean { + return node.children.some( + (child) => child.type === "element" && child.tagName === "img", + ); +} + +export const externalLinkArrow = " ↗"; export const rehypeExternalLinksOptions = { - content: { - type: "text", - value: " ↗", + content: (element) => { + if (!hasImgChild(element)) { + return { + type: "text", + value: externalLinkArrow, + }; + } }, contentProperties: { class: "external-link", diff --git a/src/plugins/rehype/heading-slugs.ts b/src/plugins/rehype/heading-slugs.ts index c7f4ae3da308618..eee76801b6f0159 100644 --- a/src/plugins/rehype/heading-slugs.ts +++ b/src/plugins/rehype/heading-slugs.ts @@ -1,7 +1,7 @@ import { toString } from "hast-util-to-string"; import { visit } from "unist-util-visit"; import GithubSlugger from "github-slugger"; -import { rehypeExternalLinksOptions } from "./external-links"; +import { externalLinkArrow } from "./external-links"; import type { Root } from "hast"; import type { MdxTextExpression } from "mdast-util-mdx-expression"; @@ -34,7 +34,7 @@ export default function () { } else { if (!element.properties.id) { const string = toString(element) - .replaceAll(rehypeExternalLinksOptions.content.value, "") + .replaceAll(externalLinkArrow, "") .trimEnd(); element.properties.id = slugs.slug(string); diff --git a/src/plugins/rehype/index.node.test.ts b/src/plugins/rehype/index.node.test.ts index 48024d1878439e6..163923e7539bbf4 100644 --- a/src/plugins/rehype/index.node.test.ts +++ b/src/plugins/rehype/index.node.test.ts @@ -15,7 +15,12 @@ describe("heading-slugs", () => { .data("settings", { fragment: true, }) - .use([rehypeParse, rehypeHeadingSlugs, rehypeStringify]) + .use([ + rehypeParse, + rehypeExternalLinks, + rehypeHeadingSlugs, + rehypeStringify, + ]) .process(html); return file.toString(); @@ -32,6 +37,16 @@ describe("heading-slugs", () => { expect(text).toMatchInlineSnapshot(`"

foo

"`); }); + + test("does not add arrow if image children", async () => { + const text = await process( + '

foo

', + ); + + expect(text).toMatchInlineSnapshot( + `"

foo

"`, + ); + }); }); describe("external-links", () => { @@ -59,6 +74,16 @@ describe("external-links", () => { expect(text).toMatchInlineSnapshot(`"foo"`); }); + + test("does not add arrow if image children", async () => { + const text = await process( + '', + ); + + expect(text).toMatchInlineSnapshot( + `""`, + ); + }); }); describe("autolink-headings", () => { diff --git a/src/util/props.node.test.ts b/src/util/props.node.test.ts index c30e4af9a2427c7..a02e7b8a3b5da11 100644 --- a/src/util/props.node.test.ts +++ b/src/util/props.node.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; import { generateDescription } from "./props"; -import { rehypeExternalLinksOptions } from "~/plugins/rehype/external-links"; +import { externalLinkArrow } from "~/plugins/rehype/external-links"; describe("description", () => { describe("markdown", () => { @@ -13,10 +13,8 @@ describe("description", () => { }); test("removes external link icon", async () => { - const icon = rehypeExternalLinksOptions.content.value; - const desc = await generateDescription({ - markdown: `[links${icon}](/) and **${icon}stuff**`, + markdown: `[links${externalLinkArrow}](/) and **${externalLinkArrow}stuff**`, }); expect(desc).toEqual("links and \\*\\*stuff\\*\\*"); diff --git a/src/util/props.ts b/src/util/props.ts index 1e50c57a72d43cf..cf6a24b38794886 100644 --- a/src/util/props.ts +++ b/src/util/props.ts @@ -3,7 +3,7 @@ import { parse } from "node-html-parser"; import he from "he"; import { remark } from "remark"; import strip from "strip-markdown"; -import { rehypeExternalLinksOptions } from "~/plugins/rehype/external-links"; +import { externalLinkArrow } from "~/plugins/rehype/external-links"; type TableOfContentsItems = NonNullable["items"]; @@ -84,7 +84,5 @@ export async function generateDescription({ if (paragraph) description = he.decode(paragraph.innerText); } - return description - ?.replaceAll(rehypeExternalLinksOptions.content.value, "") - .trim(); + return description?.replaceAll(externalLinkArrow, "").trim(); } diff --git a/src/util/sidebar.ts b/src/util/sidebar.ts index ccc23af9bf14fd3..a593c04f9b3cd3b 100644 --- a/src/util/sidebar.ts +++ b/src/util/sidebar.ts @@ -2,7 +2,7 @@ import type { AstroGlobal } from "astro"; import type { StarlightRouteData } from "@astrojs/starlight/route-data"; import { getEntry, getCollection } from "astro:content"; -import { rehypeExternalLinksOptions } from "~/plugins/rehype/external-links"; +import { externalLinkArrow } from "~/plugins/rehype/external-links"; type Link = Extract & { order?: number; @@ -280,7 +280,7 @@ async function handleLink(link: Link): Promise { if (frontmatter.external_link && !frontmatter.sidebar.group?.hideIndex) { return { ...link, - label: link.label.concat(rehypeExternalLinksOptions.content.value), + label: link.label.concat(externalLinkArrow), href: frontmatter.external_link, badge: frontmatter.external_link.startsWith("/api") ? {