From 2a55f92d3e3fdac24a6a6b484a272dfaa130a7d0 Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Tue, 15 Oct 2024 17:52:32 +0100 Subject: [PATCH 1/3] [Docs Site] Support custom heading IDs with MDX comments --- astro.config.ts | 26 ++++++++++++++++++++++++++ package-lock.json | 2 ++ package.json | 1 + 3 files changed, 29 insertions(+) diff --git a/astro.config.ts b/astro.config.ts index 54326c36ef9d24..1b85e2aaff1beb 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -17,6 +17,7 @@ import icon from "astro-icon"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; import rehypeTitleFigure from "rehype-title-figure"; +import { visit } from "unist-util-visit"; const runLinkCheck = process.env.RUN_LINK_CHECK || false; @@ -70,6 +71,30 @@ const autolinkConfig: rehypeAutolinkHeadingsOptions = { content: () => [AnchorLinkIcon], }; +// # foo {/*bar*/} = foo +function rehypeCustomHeadingId() { + return function (tree: any) { + visit(tree, "element", function (element: any) { + if (/^h[1-6]$/.test(element.tagName)) { + const last = element.children.at(-1); + + if ( + last.type === "mdxTextExpression" && + last.value.startsWith("/*") && + last.value.endsWith("*/") + ) { + const id = last.value.slice(2, -2).trim(); + element.properties.id = id; + + const text = element.children.at(-2); + text.value = text.value.trimEnd(); + element.children.with(-2, text); + } + } + }); + }; +} + // https://astro.build/config export default defineConfig({ site: "https://developers.cloudflare.com", @@ -95,6 +120,7 @@ export default defineConfig({ rel: ["noopener"], }, ], + rehypeCustomHeadingId, rehypeSlug, [rehypeAutolinkHeadings, autolinkConfig], // @ts-expect-error TODO: fix types diff --git a/package-lock.json b/package-lock.json index 7beef3d52e7f5b..b0875fb3c9edf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "tippy.js": "^6.3.7", "tsx": "^4.19.1", "typescript": "^5.5.4", + "unist-util-visit": "^5.0.0", "vitest": "2.0.5", "wrangler": "^3.78.10", "yaml": "^2.5.1" @@ -16315,6 +16316,7 @@ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", "dev": true, + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", diff --git a/package.json b/package.json index 689cc6e9fcdcfd..7e593faa32164e 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "tippy.js": "^6.3.7", "tsx": "^4.19.1", "typescript": "^5.5.4", + "unist-util-visit": "^5.0.0", "vitest": "2.0.5", "wrangler": "^3.78.10", "yaml": "^2.5.1" From bc0d04a5e3a800bd89863d61b85221b2c72d961c Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Tue, 15 Oct 2024 17:57:04 +0100 Subject: [PATCH 2/3] Amend AnchorHeading usage guidance --- .../docs/style-guide/components/anchor-heading.mdx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/content/docs/style-guide/components/anchor-heading.mdx b/src/content/docs/style-guide/components/anchor-heading.mdx index 12635a69327c5c..64b7a3d2110246 100644 --- a/src/content/docs/style-guide/components/anchor-heading.mdx +++ b/src/content/docs/style-guide/components/anchor-heading.mdx @@ -17,7 +17,17 @@ import { AnchorHeading } from "~/components"; Markdown files (including partials) have this behavior by default, applied via rehype plugins. Therefore, the `AnchorHeading` component is usually only required when writing headings yourself inside components, or when working on non-markdown files. -Additionally, `AnchorHeading` is useful when rendering partial files into one location where there are duplicate headings (for example, when there are multiple H3 corresponding to `/#create` in one page). `AnchorHeading` allows you to explicitly define fragments, ensuring that each heading can be referred correctly with unique anchors. +To override the ID given to a heading within Markdown, add an MDX comment at the end of the line: + +```mdx +# foo {/*bar*/} +``` + +It will result in the following HTML: + +```html +foo +``` :::note From 7dbd5706c5855a9d8e63a5b409cae2c740ae9bae Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Wed, 16 Oct 2024 17:12:10 +0100 Subject: [PATCH 3/3] include rehype-slug behaviour, move to individual file --- astro.config.ts | 30 ++-------------------------- package-lock.json | 19 ------------------ package.json | 1 - plugins/rehype/heading-slugs.ts | 35 +++++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 48 deletions(-) create mode 100644 plugins/rehype/heading-slugs.ts diff --git a/astro.config.ts b/astro.config.ts index 1b85e2aaff1beb..805a8147448b61 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -4,7 +4,6 @@ import tailwind from "@astrojs/tailwind"; import starlightDocSearch from "@astrojs/starlight-docsearch"; import starlightImageZoom from "starlight-image-zoom"; import liveCode from "astro-live-code"; -import rehypeSlug from "rehype-slug"; import rehypeMermaid from "rehype-mermaid"; import rehypeAutolinkHeadings, { type Options as rehypeAutolinkHeadingsOptions, @@ -17,7 +16,7 @@ import icon from "astro-icon"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; import rehypeTitleFigure from "rehype-title-figure"; -import { visit } from "unist-util-visit"; +import rehypeHeadingSlugs from "./plugins/rehype/heading-slugs"; const runLinkCheck = process.env.RUN_LINK_CHECK || false; @@ -71,30 +70,6 @@ const autolinkConfig: rehypeAutolinkHeadingsOptions = { content: () => [AnchorLinkIcon], }; -// # foo {/*bar*/} = foo -function rehypeCustomHeadingId() { - return function (tree: any) { - visit(tree, "element", function (element: any) { - if (/^h[1-6]$/.test(element.tagName)) { - const last = element.children.at(-1); - - if ( - last.type === "mdxTextExpression" && - last.value.startsWith("/*") && - last.value.endsWith("*/") - ) { - const id = last.value.slice(2, -2).trim(); - element.properties.id = id; - - const text = element.children.at(-2); - text.value = text.value.trimEnd(); - element.children.with(-2, text); - } - } - }); - }; -} - // https://astro.build/config export default defineConfig({ site: "https://developers.cloudflare.com", @@ -120,8 +95,7 @@ export default defineConfig({ rel: ["noopener"], }, ], - rehypeCustomHeadingId, - rehypeSlug, + rehypeHeadingSlugs, [rehypeAutolinkHeadings, autolinkConfig], // @ts-expect-error TODO: fix types rehypeTitleFigure, diff --git a/package-lock.json b/package-lock.json index b0875fb3c9edf7..b7df730b5bbd69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,6 @@ "rehype-autolink-headings": "^7.1.0", "rehype-external-links": "^3.0.0", "rehype-mermaid": "^2.1.0", - "rehype-slug": "^6.0.0", "rehype-title-figure": "^0.1.2", "sharp": "^0.33.5", "solarflare-theme": "^0.0.2", @@ -13703,24 +13702,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-slug": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", - "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "github-slugger": "^2.0.0", - "hast-util-heading-rank": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/rehype-stringify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", diff --git a/package.json b/package.json index 7e593faa32164e..0e1ce05e800be5 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "rehype-autolink-headings": "^7.1.0", "rehype-external-links": "^3.0.0", "rehype-mermaid": "^2.1.0", - "rehype-slug": "^6.0.0", "rehype-title-figure": "^0.1.2", "sharp": "^0.33.5", "solarflare-theme": "^0.0.2", diff --git a/plugins/rehype/heading-slugs.ts b/plugins/rehype/heading-slugs.ts new file mode 100644 index 00000000000000..42493dd7b643ec --- /dev/null +++ b/plugins/rehype/heading-slugs.ts @@ -0,0 +1,35 @@ +import { toString } from "hast-util-to-string"; +import { visit } from "unist-util-visit"; +import GithubSlugger from "github-slugger"; + +const slugs = new GithubSlugger(); + +// # foo {/*bar*/} = foo +export default function () { + return function (tree: any) { + slugs.reset(); + + visit(tree, "element", function (element: any) { + if (/^h[1-6]$/.test(element.tagName)) { + const last = element.children.at(-1); + + if ( + last.type === "mdxTextExpression" && + last.value.startsWith("/*") && + last.value.endsWith("*/") + ) { + const id = last.value.slice(2, -2).trim(); + element.properties.id = slugs.slug(id); + + const text = element.children.at(-2); + text.value = text.value.trimEnd(); + element.children.with(-2, text); + } else { + if (!element.properties.id) { + element.properties.id = slugs.slug(toString(element)); + } + } + } + }); + }; +}