diff --git a/astro.config.ts b/astro.config.ts index 54326c36ef9d24f..805a8147448b618 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,6 +16,7 @@ import icon from "astro-icon"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; import rehypeTitleFigure from "rehype-title-figure"; +import rehypeHeadingSlugs from "./plugins/rehype/heading-slugs"; const runLinkCheck = process.env.RUN_LINK_CHECK || false; @@ -95,7 +95,7 @@ export default defineConfig({ rel: ["noopener"], }, ], - rehypeSlug, + rehypeHeadingSlugs, [rehypeAutolinkHeadings, autolinkConfig], // @ts-expect-error TODO: fix types rehypeTitleFigure, diff --git a/package-lock.json b/package-lock.json index 7beef3d52e7f5b7..b7df730b5bbd693 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", @@ -69,6 +68,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" @@ -13702,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", @@ -16315,6 +16297,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 689cc6e9fcdcfd7..0e1ce05e800be5d 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", @@ -81,6 +80,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" diff --git a/plugins/rehype/heading-slugs.ts b/plugins/rehype/heading-slugs.ts new file mode 100644 index 000000000000000..42493dd7b643ec0 --- /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)); + } + } + } + }); + }; +} diff --git a/src/content/docs/style-guide/components/anchor-heading.mdx b/src/content/docs/style-guide/components/anchor-heading.mdx index 12635a69327c5c3..64b7a3d2110246f 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