diff --git a/plans/024-docs-template-parity.md b/plans/024-docs-template-parity.md index db17cf4..421e529 100644 --- a/plans/024-docs-template-parity.md +++ b/plans/024-docs-template-parity.md @@ -7,7 +7,7 @@ - **Risk**: LOW - **Depends on**: none - **Category**: authoring | ui | accessibility -- **Status**: IN PROGRESS. The first two rounds are done (PR #7, PR #8). +- **Status**: IN PROGRESS. Rounds 1 to 3 are done (PR #7, PR #8, PR #9). The remaining gaps are ranked below. - **Planned at**: commit `3579993`, 2026-10-11 @@ -96,24 +96,41 @@ authoring components, which authors notice first when they migrate. - **Fix.** A single-line `...` in MDX now renders as a block callout. +## Delivered in round 3 (PR #9) + +- **Collapsible callouts.** `:::details Title` in Markdown and MDX renders a + native `
` callout that starts closed. +- **Code blocks.** `diff-` fences get syntax colours and +/- line + marking. Quoted terms (`"apiKey"`) are highlighted. `noLineNumbers` or + `showLineNumbers` sets line numbers per block, and a header button toggles + them. Added the Prism languages diff, python, yaml, sql, go, and rust. +- **MDX parity.** Code blocks nested inside MDX callouts and tabs get the copy + button and syntax colours. `` syncs with Markdown + `:::tabs key`. +- **Steps and cards.** Markdown `:::steps` and `:::cards` (`::card +[Title](/path)`). MDX ``, ``, and ``. + Card links accept only site paths and https URLs. +- **Search.** Pagefind records carry section, collection, version, and locale + filters. Results stay in the reader's version and locale. `docs:` and + `api:` scope a query. Ranking is tuned and title matches are boosted. + **Fix:** the command palette never loaded Pagefind, because `pagefind.js` is + an ES module that does not set `window.pagefind`. It now imports the module. +- **Translatable interface labels.** `uiStringsConfig` sets the labels for + each locale (`shared/uiStrings.js`). `generate:docs` validates it. + ## Remaining gaps, in priority order -1. **Code block polish.** Add diff fences with syntax highlighting - (`diff-ts`), word/inline highlighting, a per-block toggle for line numbers, - and collapsible callouts (`:::details`). -2. **Search quality tuning.** Use Pagefind ranking weights, metadata filters - (collection, version, locale), and heading-level result anchors. Compare - results with Starlight defaults on the same corpus. -3. **i18n UI strings.** Routing works, but check that the chrome labels - (sidebar, footer, palette) can be translated per locale. -4. **Raster social cards.** The per-page cards are SVG, and some social - networks ignore SVG `og:image`. Rendering PNGs needs a rasterizer - dependency, which needs a dependency review first. -5. **Tabs and code parity.** MDX `` do not sync by group yet. MDX code - blocks nested inside callouts or tabs stay static HTML. Markdown tabs cannot - be nested. -6. **Low priority.** An RSS/Atom feed for changelog-style collections, and - Mintlify-style `Steps`/`Card` components. +1. **Raster social cards.** The cards are still SVG. PNG output needs a + rasterizer dependency, which is not approved yet. +2. **Heading-level search results.** Pagefind custom records have no + headings, so results link to whole pages. Indexing the route HTML would + give section anchors. +3. **Search filter UI.** Scopes use a query prefix. There are no filter chips + in the palette yet. +4. **More translatable labels.** Sidebar, mindmap, and palette command labels + are still English-only. +5. **Nested Markdown tabs**, and a theme-aware diff palette per theme. +6. **Low priority.** An RSS/Atom feed for changelog-style collections. ## Verification diff --git a/public/collections.json b/public/collections.json index 27dbd2e..f0b403d 100644 --- a/public/collections.json +++ b/public/collections.json @@ -1,7 +1,7 @@ { "contractVersion": 1, "schemaVersion": 1, - "generated": "2026-10-11T13:11:16.429Z", + "generated": "2026-10-11T16:01:47.501Z", "collections": [ { "id": "docs", @@ -15,7 +15,7 @@ "references": "/docs-references.json" }, "schemaVersion": 1, - "count": 32 + "count": 34 } ] } diff --git a/public/docs-collection.json b/public/docs-collection.json index 97d5a44..c3a5177 100644 --- a/public/docs-collection.json +++ b/public/docs-collection.json @@ -11,8 +11,8 @@ "manifest": "/docs-collection.json", "references": "/docs-references.json" }, - "generated": "2026-10-11T13:11:16.429Z", - "count": 32, + "generated": "2026-10-11T16:01:47.501Z", + "count": 34, "schema": { "title": { "type": "string" @@ -190,6 +190,18 @@ "path": "developer-guides/callouts-in-mdx", "tags": ["callouts", "admonitions", "code", "mdx"] }, + { + "type": "file", + "name": "Steps And Cards.md", + "path": "developer-guides/steps-and-cards", + "tags": ["steps", "cards", "components", "layout"] + }, + { + "type": "file", + "name": "Translating The Interface.md", + "path": "developer-guides/translating-the-interface", + "tags": ["i18n", "locales", "labels", "translation"] + }, { "type": "file", "name": "Math And Diagrams.md", @@ -624,6 +636,25 @@ }, "sourcePath": "src/docs/content/developer-guides/math-in-mdx.mdx" }, + { + "path": "developer-guides/steps-and-cards", + "title": "Steps And Cards", + "description": "Numbered steps and link cards for guides and landing pages.", + "frontmatter": { + "description": "Numbered steps and link cards for guides and landing pages." + }, + "metadata": { + "title": "Steps And Cards", + "description": "Numbered steps and link cards for guides and landing pages.", + "canonicalPath": "/docs/developer-guides/steps-and-cards", + "contentFormat": "markdown", + "sourcePath": "src/docs/content/developer-guides/steps-and-cards.md", + "collection": "docs", + "routePrefix": "/docs", + "schemaVersion": 1 + }, + "sourcePath": "src/docs/content/developer-guides/steps-and-cards.md" + }, { "path": "developer-guides/tailwind-integration", "title": "Tailwind Integration", @@ -658,6 +689,25 @@ }, "sourcePath": "src/docs/content/developer-guides/themes.md" }, + { + "path": "developer-guides/translating-the-interface", + "title": "Translating The Interface", + "description": "Translate or rename the labels in the docs chrome, per locale.", + "frontmatter": { + "description": "Translate or rename the labels in the docs chrome, per locale." + }, + "metadata": { + "title": "Translating The Interface", + "description": "Translate or rename the labels in the docs chrome, per locale.", + "canonicalPath": "/docs/developer-guides/translating-the-interface", + "contentFormat": "markdown", + "sourcePath": "src/docs/content/developer-guides/translating-the-interface.md", + "collection": "docs", + "routePrefix": "/docs", + "schemaVersion": 1 + }, + "sourcePath": "src/docs/content/developer-guides/translating-the-interface.md" + }, { "path": "developer-guides/ui-configuration", "title": "UI Configuration", diff --git a/public/docs-content/developer-guides/callouts-and-code.json b/public/docs-content/developer-guides/callouts-and-code.json index 5ca65e9..b0152f1 100644 --- a/public/docs-content/developer-guides/callouts-and-code.json +++ b/public/docs-content/developer-guides/callouts-and-code.json @@ -5,7 +5,7 @@ "frontmatter": { "description": "Callouts (admonitions), GitHub-style alerts, code block titles, line highlighting, and diffs." }, - "content": "# Callouts And Code Blocks\n\nPapers Markdown supports callouts for asides and warnings, plus code fence\nmetadata for filenames, highlighted lines, and diffs. These features work in\n`.md` pages.\n\n## Callouts\n\nWrap content in `:::type` and close it with `:::`. The five types are `note`,\n`tip`, `info`, `warning`, and `danger`. Text after the type becomes the title.\n\n```md\n:::note\nCallouts can contain **Markdown**, lists, and code.\n:::\n\n:::warning Breaking change\n`generate:docs` now validates frontmatter.\n:::\n```\n\n:::note\nCallouts can contain **Markdown**, lists, and code.\n:::\n\n:::tip Faster previews\nRun `npm run dev` to regenerate artifacts and start Vite in one step.\n:::\n\n:::info\nSearch results come from the Pagefind index built with `npm run build`.\n:::\n\n:::warning Breaking change\n`generate:docs` now validates frontmatter.\n:::\n\n:::danger\nNever commit deployment credentials to `wrangler.toml`.\n:::\n\n### Nesting\n\nGive the outer fence more colons than the inner one:\n\n```md\n::::info Outer\nOuter text.\n\n:::tip Inner\nInner text.\n:::\n::::\n```\n\n::::info Outer\nOuter text.\n\n:::tip Inner\nInner text.\n:::\n::::\n\n## GitHub-Style Alerts\n\nThe GitHub alert syntax renders the same way, so READMEs and docs can share\ncontent. `IMPORTANT` uses the info style and `CAUTION` uses the danger style.\n\n```md\n> [!TIP]\n> Alerts written for GitHub render as callouts here too.\n```\n\n> [!TIP]\n> Alerts written for GitHub render as callouts here too.\n\n> [!CAUTION]\n> Deleting `dist/` removes the last build output.\n\n## Code Block Titles\n\nAdd `title=\"...\"` after the language to show a filename in the code block header.\n\n````md\n```ts title=\"src/greet.ts\"\nexport const greet = (name: string) => `Hello, ${name}`;\n```\n````\n\n```ts title=\"src/greet.ts\"\nexport const greet = (name: string) => `Hello, ${name}`;\n```\n\n## Line Highlighting\n\nList line numbers or ranges in braces. You can combine them with a title.\n\n````md\n```ts title=\"vite.config.ts\" {2,4-5}\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n plugins: [react()],\n});\n```\n````\n\n```ts title=\"vite.config.ts\" {2,4-5}\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n plugins: [react()],\n});\n```\n\n## Diffs\n\nA `diff` fence marks lines that start with `+` as added and lines that start\nwith `-` as removed. The `+` and `-` markers stay visible, so the change does\nnot depend on color alone.\n\n````md\n```diff title=\"package.json\"\n \"scripts\": {\n- \"build\": \"vite build\"\n+ \"build\": \"npm run generate:docs && vite build\"\n }\n```\n````\n\n```diff title=\"package.json\"\n \"scripts\": {\n- \"build\": \"vite build\"\n+ \"build\": \"npm run generate:docs && vite build\"\n }\n```\n\nEvery code block keeps its copy button. The copy button copies the code\nexactly as written, including diff markers.\n\n## Content Tabs\n\nWrap tabs in `:::tabs` and start each tab with a `::tab Label` line. Tabs can\nhold any Markdown, including code blocks.\n\nAdd a group key after `tabs` to keep tab sets in sync. Choosing a tab in one\nset switches every set with the same key on the page. The choice is saved in\nthe browser, so other pages open on the same tab.\n\n````md\n:::tabs pkg\n::tab npm\n\n```bash\nnpm install papers\n```\n\n::tab pnpm\n\n```bash\npnpm add papers\n```\n\n:::\n````\n\n:::tabs pkg\n::tab npm\n\n```bash\nnpm install papers\n```\n\n::tab pnpm\n\n```bash\npnpm add papers\n```\n\n:::\n\nThis second set uses the same `pkg` key, so it follows the first one:\n\n:::tabs pkg\n::tab npm\nRun `npm run dev` to start the dev server.\n::tab pnpm\nRun `pnpm dev` to start the dev server.\n:::\n\nTabs without a group key switch on their own. If a panel contains a callout,\ngive the tabs fence more colons than the callout, for example `::::tabs`.\nTabs cannot be nested.\n", + "content": "# Callouts And Code Blocks\n\nPapers Markdown supports callouts for asides and warnings, plus code fence\nmetadata for filenames, highlighted lines, and diffs. These features work in\n`.md` pages.\n\n## Callouts\n\nWrap content in `:::type` and close it with `:::`. The five types are `note`,\n`tip`, `info`, `warning`, and `danger`. Text after the type becomes the title.\n\n```md\n:::note\nCallouts can contain **Markdown**, lists, and code.\n:::\n\n:::warning Breaking change\n`generate:docs` now validates frontmatter.\n:::\n```\n\n:::note\nCallouts can contain **Markdown**, lists, and code.\n:::\n\n:::tip Faster previews\nRun `npm run dev` to regenerate artifacts and start Vite in one step.\n:::\n\n:::info\nSearch results come from the Pagefind index built with `npm run build`.\n:::\n\n:::warning Breaking change\n`generate:docs` now validates frontmatter.\n:::\n\n:::danger\nNever commit deployment credentials to `wrangler.toml`.\n:::\n\n### Nesting\n\nGive the outer fence more colons than the inner one:\n\n```md\n::::info Outer\nOuter text.\n\n:::tip Inner\nInner text.\n:::\n::::\n```\n\n::::info Outer\nOuter text.\n\n:::tip Inner\nInner text.\n:::\n::::\n\n### Collapsible Callouts\n\n`:::details Title` renders a callout that starts closed. Readers open it by\nclicking the title or pressing Enter or Space on it.\n\n```md\n:::details Show the full config\nThe full configuration lives in `shared/documentation-config.js`.\n:::\n```\n\n:::details Show the full config\nThe full configuration lives in `shared/documentation-config.js`.\n:::\n\n## GitHub-Style Alerts\n\nThe GitHub alert syntax renders the same way, so READMEs and docs can share\ncontent. `IMPORTANT` uses the info style and `CAUTION` uses the danger style.\n\n```md\n> [!TIP]\n> Alerts written for GitHub render as callouts here too.\n```\n\n> [!TIP]\n> Alerts written for GitHub render as callouts here too.\n\n> [!CAUTION]\n> Deleting `dist/` removes the last build output.\n\n## Code Block Titles\n\nAdd `title=\"...\"` after the language to show a filename in the code block header.\n\n````md\n```ts title=\"src/greet.ts\"\nexport const greet = (name: string) => `Hello, ${name}`;\n```\n````\n\n```ts title=\"src/greet.ts\"\nexport const greet = (name: string) => `Hello, ${name}`;\n```\n\n## Line Highlighting\n\nList line numbers or ranges in braces. You can combine them with a title.\n\n````md\n```ts title=\"vite.config.ts\" {2,4-5}\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n plugins: [react()],\n});\n```\n````\n\n```ts title=\"vite.config.ts\" {2,4-5}\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n plugins: [react()],\n});\n```\n\n## Diffs\n\nA `diff` fence marks lines that start with `+` as added and lines that start\nwith `-` as removed. The `+` and `-` markers stay visible, so the change does\nnot depend on color alone.\n\n````md\n```diff title=\"package.json\"\n \"scripts\": {\n- \"build\": \"vite build\"\n+ \"build\": \"npm run generate:docs && vite build\"\n }\n```\n````\n\n```diff title=\"package.json\"\n \"scripts\": {\n- \"build\": \"vite build\"\n+ \"build\": \"npm run generate:docs && vite build\"\n }\n```\n\nTo colour the code as well, add the language after `diff-`, for example\n`diff-ts` or `diff-json`:\n\n```diff-ts title=\"greet.ts\"\n export function greet(name: string) {\n- return 'Hello ' + name;\n+ return `Hello, ${name}!`;\n }\n```\n\n## Word Highlighting\n\nPut a term in quotes after the language to highlight every place it appears.\nYou can list up to 10 terms.\n\n````md\n```ts \"apiKey\" \"fetchDocs\"\nconst apiKey = process.env.DOCS_API_KEY;\nawait fetchDocs({ apiKey });\n```\n````\n\n```ts \"apiKey\" \"fetchDocs\"\nconst apiKey = process.env.DOCS_API_KEY;\nawait fetchDocs({ apiKey });\n```\n\n## Line Numbers\n\nCode blocks show line numbers by default. Add `noLineNumbers` (or\n`showLineNumbers=false`) to hide them for one block. Readers can also show\nor hide line numbers with the button in any code block's header.\n\n````md\n```bash noLineNumbers\nnpm run build\n```\n````\n\n```bash noLineNumbers\nnpm run build\n```\n\nEvery code block keeps its copy button. The copy button copies the code\nexactly as written, including diff markers.\n\n## Content Tabs\n\nWrap tabs in `:::tabs` and start each tab with a `::tab Label` line. Tabs can\nhold any Markdown, including code blocks.\n\nAdd a group key after `tabs` to keep tab sets in sync. Choosing a tab in one\nset switches every set with the same key on the page. The choice is saved in\nthe browser, so other pages open on the same tab.\n\n````md\n:::tabs pkg\n::tab npm\n\n```bash\nnpm install papers\n```\n\n::tab pnpm\n\n```bash\npnpm add papers\n```\n\n:::\n````\n\n:::tabs pkg\n::tab npm\n\n```bash\nnpm install papers\n```\n\n::tab pnpm\n\n```bash\npnpm add papers\n```\n\n:::\n\nThis second set uses the same `pkg` key, so it follows the first one:\n\n:::tabs pkg\n::tab npm\nRun `npm run dev` to start the dev server.\n::tab pnpm\nRun `pnpm dev` to start the dev server.\n:::\n\nTabs without a group key switch on their own. If a panel contains a callout,\ngive the tabs fence more colons than the callout, for example `::::tabs`.\nTabs cannot be nested.\n", "contentFormat": "markdown", "toc": [ { @@ -23,6 +23,11 @@ "id": "nesting", "text": "Nesting" }, + { + "depth": 3, + "id": "collapsible-callouts", + "text": "Collapsible Callouts" + }, { "depth": 2, "id": "github-style-alerts", @@ -43,6 +48,16 @@ "id": "diffs", "text": "Diffs" }, + { + "depth": 2, + "id": "word-highlighting", + "text": "Word Highlighting" + }, + { + "depth": 2, + "id": "line-numbers", + "text": "Line Numbers" + }, { "depth": 2, "id": "content-tabs", diff --git a/public/docs-content/developer-guides/callouts-in-mdx.json b/public/docs-content/developer-guides/callouts-in-mdx.json index 823bd19..c4174f9 100644 --- a/public/docs-content/developer-guides/callouts-in-mdx.json +++ b/public/docs-content/developer-guides/callouts-in-mdx.json @@ -6,7 +6,7 @@ "title": "Callouts And Code In MDX", "description": "Use callouts, GitHub alerts, and code fence titles, line highlights, and diffs in MDX pages." }, - "content": "

Callouts And Code In MDX

\n

.mdx pages support the same callout and code fence syntax as\nMarkdown pages. They are compiled\nto sanitized static HTML at build time.

\n

Callouts

\n

Write :::type Title on the first line and ::: on the last line. The five\ntypes are note, tip, info, warning, and danger. In MDX, use a\nplain-text title.

\n

Note

MDX callouts hold Markdown and code.

\n

Breaking change

generate:docs now validates frontmatter.

\n

Outer

Give the outer fence more colons to nest callouts.

Inner

Inner callout.

\n

The <Callout> shortcode renders the same callout. type accepts note,\ntip, info, warning, danger, important, or caution. Put the\nshortcode on its own lines.

\n

Shortcode form

Never commit deployment credentials.

\n

GitHub Alerts

\n

Tip

GitHub alert syntax also works in MDX.

\n

Code Fence Metadata

\n
setup.sh
npm ci\nnpm run generate:docs\nnpm run dev
\n
package.json
-    \"build\": \"vite build\"\n+    \"build\": \"npm run generate:docs && vite build\"
\n

Code blocks in MDX pages use the same viewer as Markdown pages, with a copy button\nand syntax colours.

", + "content": "

Callouts And Code In MDX

\n

.mdx pages support the same callout and code fence syntax as\nMarkdown pages. They are compiled\nto sanitized static HTML at build time.

\n

Callouts

\n

Write :::type Title on the first line and ::: on the last line. The five\ntypes are note, tip, info, warning, and danger. In MDX, use a\nplain-text title.

\n

Note

MDX callouts hold Markdown and code.

\n

Breaking change

generate:docs now validates frontmatter.

\n

Outer

Give the outer fence more colons to nest callouts.

Inner

Inner callout.

\n

:::details Title makes a collapsible callout that starts closed.

\n
Show more

Collapsible callouts work in MDX too.

\n

The <Callout> shortcode renders the same callout. type accepts note,\ntip, info, warning, danger, important, or caution. Put the\nshortcode on its own lines.

\n

Shortcode form

Never commit deployment credentials.

\n

Code Inside Callouts And Tabs

\n

Code blocks inside callouts and <Tabs> get the same copy button and syntax\ncolours as top-level blocks.

\n

Install command

install.sh
npm install papers
\n

Synced Tabs

\n

Give <Tabs> a group key to keep tab sets in sync. Markdown pages use the\nsame keys (:::tabs pkg), so a choice made on one page carries over to the\nother.

\n
npm
npm run dev\n
pnpm
pnpm dev\n
\n

Steps And Cards

\n
    \n
  1. Install the dependencies.
  2. \n
  3. Start the dev server.
  4. \n
\n

Steps and cards

The Markdown syntax for steps and cards.

Callouts and code

Callouts and code blocks in Markdown.

\n

GitHub Alerts

\n

Tip

GitHub alert syntax also works in MDX.

\n

Code Fence Metadata

\n
setup.sh
npm ci\nnpm run generate:docs\nnpm run dev
\n
package.json
-    \"build\": \"vite build\"\n+    \"build\": \"npm run generate:docs && vite build\"
\n

Code blocks in MDX pages use the same viewer as Markdown pages, with a copy button\nand syntax colours.

", "contentFormat": "html", "toc": [ { @@ -19,6 +19,21 @@ "id": "callouts", "text": "Callouts" }, + { + "depth": 2, + "id": "code-inside-callouts-and-tabs", + "text": "Code Inside Callouts And Tabs" + }, + { + "depth": 2, + "id": "synced-tabs", + "text": "Synced Tabs" + }, + { + "depth": 2, + "id": "steps-and-cards", + "text": "Steps And Cards" + }, { "depth": 2, "id": "github-alerts", @@ -44,6 +59,14 @@ { "target": "/docs/developer-guides/callouts-and-code", "line": 9 + }, + { + "target": "/docs/developer-guides/steps-and-cards", + "line": 94 + }, + { + "target": "/docs/developer-guides/callouts-and-code", + "line": 97 } ], "links": [ @@ -51,6 +74,16 @@ "target": "/docs/developer-guides/callouts-and-code", "line": 9, "type": "internal" + }, + { + "target": "/docs/developer-guides/steps-and-cards", + "line": 94, + "type": "internal" + }, + { + "target": "/docs/developer-guides/callouts-and-code", + "line": 97, + "type": "internal" } ], "sourcePath": "src/docs/content/developer-guides/callouts-in-mdx.mdx" diff --git a/public/docs-content/developer-guides/steps-and-cards.json b/public/docs-content/developer-guides/steps-and-cards.json new file mode 100644 index 0000000..3fe56a7 --- /dev/null +++ b/public/docs-content/developer-guides/steps-and-cards.json @@ -0,0 +1,65 @@ +{ + "path": "developer-guides/steps-and-cards", + "title": "Steps And Cards", + "description": "Numbered steps and link cards for guides and landing pages.", + "frontmatter": { + "description": "Numbered steps and link cards for guides and landing pages." + }, + "content": "# Steps And Cards\n\nUse steps for procedures that readers follow in order, and cards to point\nreaders to other pages.\n\n## Steps\n\nWrap an ordered list in `:::steps`. Each list item becomes a numbered step.\n\n```md\n:::steps\n\n1. Install the dependencies.\n2. Start the dev server.\n3. Open the local URL.\n :::\n```\n\n:::steps\n\n1. Install the dependencies.\n\n ```bash\n npm install\n ```\n\n2. Start the dev server.\n\n ```bash\n npm run dev\n ```\n\n3. Open the URL that the dev server prints.\n\n:::\n\n## Cards\n\nPut cards in `:::cards`. Start each card with a `::card Title` line. To link\nthe card, write the title as a Markdown link. Links must be site paths that\nstart with `/` or `https://` URLs. Other links are dropped and the title shows\nas plain text.\n\n```md\n:::cards\n::card [Callouts and code](/docs/developer-guides/callouts-and-code)\nAdmonitions, code titles, and diffs.\n::card [MDX authoring](/docs/developer-guides/callouts-in-mdx)\nThe same features in `.mdx` pages.\n:::\n```\n\n:::cards\n::card [Callouts and code](/docs/developer-guides/callouts-and-code)\nAdmonitions, code titles, and diffs.\n::card [MDX authoring](/docs/developer-guides/callouts-in-mdx)\nThe same features in `.mdx` pages.\n:::\n\n## In MDX\n\nMDX pages use ``, ``, and ``:\n\n```mdx\n\n\n1. Install the dependencies.\n2. Start the dev server.\n\n\n\n\n \n Admonitions, code titles, and diffs.\n \n\n```\n", + "contentFormat": "markdown", + "toc": [ + { + "depth": 1, + "id": "steps-and-cards", + "text": "Steps And Cards" + }, + { + "depth": 2, + "id": "steps", + "text": "Steps" + }, + { + "depth": 2, + "id": "cards", + "text": "Cards" + }, + { + "depth": 2, + "id": "in-mdx", + "text": "In MDX" + } + ], + "metadata": { + "title": "Steps And Cards", + "description": "Numbered steps and link cards for guides and landing pages.", + "canonicalPath": "/docs/developer-guides/steps-and-cards", + "contentFormat": "markdown", + "sourcePath": "src/docs/content/developer-guides/steps-and-cards.md", + "collection": "docs", + "routePrefix": "/docs", + "schemaVersion": 1 + }, + "references": [ + { + "target": "/docs/developer-guides/callouts-and-code", + "line": 58 + }, + { + "target": "/docs/developer-guides/callouts-in-mdx", + "line": 60 + } + ], + "links": [ + { + "target": "/docs/developer-guides/callouts-and-code", + "line": 58, + "type": "internal" + }, + { + "target": "/docs/developer-guides/callouts-in-mdx", + "line": 60, + "type": "internal" + } + ], + "sourcePath": "src/docs/content/developer-guides/steps-and-cards.md" +} diff --git a/public/docs-content/developer-guides/translating-the-interface.json b/public/docs-content/developer-guides/translating-the-interface.json new file mode 100644 index 0000000..73550c0 --- /dev/null +++ b/public/docs-content/developer-guides/translating-the-interface.json @@ -0,0 +1,45 @@ +{ + "path": "developer-guides/translating-the-interface", + "title": "Translating The Interface", + "description": "Translate or rename the labels in the docs chrome, per locale.", + "frontmatter": { + "description": "Translate or rename the labels in the docs chrome, per locale." + }, + "content": "# Translating The Interface\n\nPage content is translated by adding locale folders. The labels around the\ncontent, such as **Previous**, **Next**, **Last updated**, and the copy button,\ncome from `shared/uiStrings.js`. You can translate or rename them per locale.\n\n## Set Labels For A Locale\n\nAdd the labels to `uiStringsConfig` in `shared/documentation-config.js`. Use\nthe locale codes from `i18nConfig.locales`. Labels that you leave out keep the\nEnglish default.\n\n```js title=\"shared/documentation-config.js\"\nexport const i18nConfig = {\n enabled: true,\n defaultLocale: 'en',\n locales: ['en', 'fr'],\n labels: { en: 'EN', fr: 'FR' },\n};\n\nexport const uiStringsConfig = {\n fr: {\n previous: 'Précédent',\n next: 'Suivant',\n lastUpdated: 'Mis à jour le',\n searchDocs: 'Rechercher',\n copyCode: 'Copier',\n },\n};\n```\n\nTo rename a label on a site with one language, add an entry for the default\nlocale, for example `en: { edit: 'Edit this page' }`.\n\nThe page language comes from the URL. Dates in the **Last updated** line use\nthe same locale.\n\n## Available Labels\n\n| Key | Default |\n| ------------------- | -------------------------------------------- |\n| `previous` | Previous |\n| `next` | Next |\n| `adjacentPages` | Adjacent documentation pages (screen reader) |\n| `lastUpdated` | Last updated |\n| `edit` | edit |\n| `issue` | issue |\n| `source` | source |\n| `pageSourceActions` | Page source actions (screen reader) |\n| `searchDocs` | Search docs |\n| `searchPlaceholder` | Search documentation or type a command... |\n| `noResults` | No results found for |\n| `copyCode` | Copy to clipboard |\n| `copied` | Copied |\n| `showLineNumbers` | Show line numbers |\n| `hideLineNumbers` | Hide line numbers |\n| `referencedBy` | Referenced by |\n\n## Validation\n\n`npm run generate:docs` (and so `npm run build`) fails if `uiStringsConfig`\nuses a locale that is not in `i18nConfig.locales`, an unknown key, or an empty\nvalue. This keeps a typo from hiding a label.\n", + "contentFormat": "markdown", + "toc": [ + { + "depth": 1, + "id": "translating-the-interface", + "text": "Translating The Interface" + }, + { + "depth": 2, + "id": "set-labels-for-a-locale", + "text": "Set Labels For A Locale" + }, + { + "depth": 2, + "id": "available-labels", + "text": "Available Labels" + }, + { + "depth": 2, + "id": "validation", + "text": "Validation" + } + ], + "metadata": { + "title": "Translating The Interface", + "description": "Translate or rename the labels in the docs chrome, per locale.", + "canonicalPath": "/docs/developer-guides/translating-the-interface", + "contentFormat": "markdown", + "sourcePath": "src/docs/content/developer-guides/translating-the-interface.md", + "collection": "docs", + "routePrefix": "/docs", + "schemaVersion": 1 + }, + "references": [], + "links": [], + "sourcePath": "src/docs/content/developer-guides/translating-the-interface.md" +} diff --git a/public/docs-content/user-guide/command-palette.json b/public/docs-content/user-guide/command-palette.json index af4ca5a..a84dc79 100644 --- a/public/docs-content/user-guide/command-palette.json +++ b/public/docs-content/user-guide/command-palette.json @@ -3,7 +3,7 @@ "title": "Command Palette", "description": "The command palette is the fastest way to move through the site.", "frontmatter": {}, - "content": "# Command Palette\n\nThe command palette is the fastest way to move through the site.\n\n## Open It\n\n- Mac: `Cmd + K`\n- Windows / Linux: `Ctrl + K`\n\n## What It Can Show\n\n- docs pages from the shared navigation tree\n- built-in actions such as theme changes\n- FAQ answers inline inside the palette\n- quick links like the homepage and `llms.txt`\n- Pagefind full-text results after a production build\n\n## Keyboard Controls\n\n- `Up` and `Down` move through results\n- `Enter` opens a result or previews an FAQ answer\n- `Esc` closes the palette\n\n## Related Shortcut\n\n`Cmd/Ctrl + I` toggles the theme.\n\n## Search Notes\n\nLocal navigation results are always available.\n\nPagefind results depend on the production search index, so validate those with `npm run build` rather than only local dev.\n\n## Next Steps\n\n- [Advanced Features](/docs/user-guide/advanced-features)\n- [Basic Usage](/docs/user-guide/basic-usage)\n", + "content": "# Command Palette\n\nThe command palette is the fastest way to move through the site.\n\n## Open It\n\n- Mac: `Cmd + K`\n- Windows / Linux: `Ctrl + K`\n\n## What It Can Show\n\n- docs pages from the shared navigation tree\n- built-in actions such as theme changes\n- FAQ answers inline inside the palette\n- quick links like the homepage and `llms.txt`\n- Pagefind full-text results after a production build\n\n## Keyboard Controls\n\n- `Up` and `Down` move through results\n- `Enter` opens a result or previews an FAQ answer\n- `Esc` closes the palette\n\n## Related Shortcut\n\n`Cmd/Ctrl + I` toggles the theme.\n\n## Search Notes\n\nLocal navigation results are always available.\n\nPagefind results depend on the production search index, so validate those with `npm run build` rather than only local dev.\n\n### Scoping And Ranking\n\n- Start a query with `docs:` to search only guides, or `api:` to search only\n the OpenAPI reference. For example, `api: users`.\n- On a versioned or translated page, guide results come from the same version\n and language as the page you are reading.\n- Pages whose title matches your query rank first. The ranking settings live in\n `src/lib/searchRanking.ts`.\n\n## Next Steps\n\n- [Advanced Features](/docs/user-guide/advanced-features)\n- [Basic Usage](/docs/user-guide/basic-usage)\n", "contentFormat": "markdown", "toc": [ { @@ -36,6 +36,11 @@ "id": "search-notes", "text": "Search Notes" }, + { + "depth": 3, + "id": "scoping-and-ranking", + "text": "Scoping And Ranking" + }, { "depth": 2, "id": "next-steps", @@ -55,22 +60,22 @@ "references": [ { "target": "/docs/user-guide/advanced-features", - "line": 36 + "line": 45 }, { "target": "/docs/user-guide/basic-usage", - "line": 37 + "line": 46 } ], "links": [ { "target": "/docs/user-guide/advanced-features", - "line": 36, + "line": 45, "type": "internal" }, { "target": "/docs/user-guide/basic-usage", - "line": 37, + "line": 46, "type": "internal" } ], diff --git a/public/docs-index.json b/public/docs-index.json index a125116..c394117 100644 --- a/public/docs-index.json +++ b/public/docs-index.json @@ -1,5 +1,5 @@ { - "generated": "2026-10-11T13:11:16.429Z", + "generated": "2026-10-11T16:01:47.501Z", "paths": [ "api-reference/content-pipeline", "api-reference/openapi", @@ -19,8 +19,10 @@ "developer-guides/icon-customization", "developer-guides/math-and-diagrams", "developer-guides/math-in-mdx", + "developer-guides/steps-and-cards", "developer-guides/tailwind-integration", "developer-guides/themes", + "developer-guides/translating-the-interface", "developer-guides/ui-configuration", "getting-started/installation", "getting-started/introduction", @@ -34,7 +36,7 @@ "user-guide/interactive-map", "user-guide/troubleshooting" ], - "count": 32, + "count": 34, "titles": { "api-reference/content-pipeline": "Content Pipeline", "api-reference/openapi": "OpenAPI Explorer", @@ -54,8 +56,10 @@ "developer-guides/icon-customization": "Icon Customization", "developer-guides/math-and-diagrams": "Math And Diagrams", "developer-guides/math-in-mdx": "Math And Diagrams In MDX", + "developer-guides/steps-and-cards": "Steps And Cards", "developer-guides/tailwind-integration": "Tailwind Integration", "developer-guides/themes": "Themes", + "developer-guides/translating-the-interface": "Translating The Interface", "developer-guides/ui-configuration": "UI Configuration", "getting-started/installation": "Installation", "getting-started/introduction": "Introduction", diff --git a/public/docs-references.json b/public/docs-references.json index 182d2ba..70f3479 100644 --- a/public/docs-references.json +++ b/public/docs-references.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "generated": "2026-10-11T13:11:16.429Z", + "generated": "2026-10-11T16:01:47.501Z", "collection": "docs", "nodes": [ { @@ -111,6 +111,12 @@ "title": "Math And Diagrams In MDX", "description": "KaTeX math and Mermaid diagrams in .mdx pages, alongside MDX shortcodes." }, + { + "path": "developer-guides/steps-and-cards", + "href": "/docs/developer-guides/steps-and-cards", + "title": "Steps And Cards", + "description": "Numbered steps and link cards for guides and landing pages." + }, { "path": "developer-guides/tailwind-integration", "href": "/docs/developer-guides/tailwind-integration", @@ -123,6 +129,12 @@ "title": "Themes", "description": "papers ships with installable themes under themes/. Themes override color, typography, radius, and optional icon sets without forking the docs shell." }, + { + "path": "developer-guides/translating-the-interface", + "href": "/docs/developer-guides/translating-the-interface", + "title": "Translating The Interface", + "description": "Translate or rename the labels in the docs chrome, per locale." + }, { "path": "developer-guides/ui-configuration", "href": "/docs/developer-guides/ui-configuration", @@ -245,6 +257,30 @@ "href": "/docs/developer-guides/callouts-and-code", "line": 9 }, + { + "source": "developer-guides/callouts-in-mdx", + "target": "developer-guides/callouts-and-code", + "href": "/docs/developer-guides/callouts-and-code", + "line": 97 + }, + { + "source": "developer-guides/callouts-in-mdx", + "target": "developer-guides/steps-and-cards", + "href": "/docs/developer-guides/steps-and-cards", + "line": 94 + }, + { + "source": "developer-guides/steps-and-cards", + "target": "developer-guides/callouts-and-code", + "href": "/docs/developer-guides/callouts-and-code", + "line": 58 + }, + { + "source": "developer-guides/steps-and-cards", + "target": "developer-guides/callouts-in-mdx", + "href": "/docs/developer-guides/callouts-in-mdx", + "line": 60 + }, { "source": "developer-guides/ui-configuration", "target": "developer-guides/themes", @@ -333,13 +369,13 @@ "source": "user-guide/command-palette", "target": "user-guide/advanced-features", "href": "/docs/user-guide/advanced-features", - "line": 36 + "line": 45 }, { "source": "user-guide/command-palette", "target": "user-guide/basic-usage", "href": "/docs/user-guide/basic-usage", - "line": 37 + "line": 46 }, { "source": "user-guide/configuration", @@ -423,9 +459,25 @@ "source": "developer-guides/callouts-in-mdx", "href": "/docs/developer-guides/callouts-in-mdx", "line": 9 + }, + { + "source": "developer-guides/callouts-in-mdx", + "href": "/docs/developer-guides/callouts-in-mdx", + "line": 97 + }, + { + "source": "developer-guides/steps-and-cards", + "href": "/docs/developer-guides/steps-and-cards", + "line": 58 + } + ], + "developer-guides/callouts-in-mdx": [ + { + "source": "developer-guides/steps-and-cards", + "href": "/docs/developer-guides/steps-and-cards", + "line": 60 } ], - "developer-guides/callouts-in-mdx": [], "developer-guides/character-notes": [], "developer-guides/code-examples": [ { @@ -444,6 +496,13 @@ "developer-guides/icon-customization": [], "developer-guides/math-and-diagrams": [], "developer-guides/math-in-mdx": [], + "developer-guides/steps-and-cards": [ + { + "source": "developer-guides/callouts-in-mdx", + "href": "/docs/developer-guides/callouts-in-mdx", + "line": 94 + } + ], "developer-guides/tailwind-integration": [], "developer-guides/themes": [ { @@ -452,6 +511,7 @@ "line": 30 } ], + "developer-guides/translating-the-interface": [], "developer-guides/ui-configuration": [ { "source": "user-guide/configuration", @@ -491,7 +551,7 @@ { "source": "user-guide/command-palette", "href": "/docs/user-guide/command-palette", - "line": 36 + "line": 45 } ], "user-guide/basic-usage": [ @@ -508,7 +568,7 @@ { "source": "user-guide/command-palette", "href": "/docs/user-guide/command-palette", - "line": 37 + "line": 46 }, { "source": "user-guide/interactive-map", diff --git a/public/llms-full.txt b/public/llms-full.txt index 00fbfc2..55d52e7 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -553,6 +553,15 @@ Local navigation results are always available. Pagefind results depend on the production search index, so validate those with `npm run build` rather than only local dev. +### Scoping And Ranking + +- Start a query with `docs:` to search only guides, or `api:` to search only + the OpenAPI reference. For example, `api: users`. +- On a versioned or translated page, guide results come from the same version + and language as the page you are reading. +- Pages whose title matches your query rank first. The ranking settings live in + `src/lib/searchRanking.ts`. + ## Next Steps - [Advanced Features](/docs/user-guide/advanced-features) @@ -1126,6 +1135,21 @@ Inner text. ::: :::: +### Collapsible Callouts + +`:::details Title` renders a callout that starts closed. Readers open it by +clicking the title or pressing Enter or Space on it. + +```md +:::details Show the full config +The full configuration lives in `shared/documentation-config.js`. +::: +``` + +:::details Show the full config +The full configuration lives in `shared/documentation-config.js`. +::: + ## GitHub-Style Alerts The GitHub alert syntax renders the same way, so READMEs and docs can share @@ -1202,6 +1226,49 @@ not depend on color alone. } ``` +To colour the code as well, add the language after `diff-`, for example +`diff-ts` or `diff-json`: + +```diff-ts title="greet.ts" + export function greet(name: string) { +- return 'Hello ' + name; ++ return `Hello, ${name}!`; + } +``` + +## Word Highlighting + +Put a term in quotes after the language to highlight every place it appears. +You can list up to 10 terms. + +````md +```ts "apiKey" "fetchDocs" +const apiKey = process.env.DOCS_API_KEY; +await fetchDocs({ apiKey }); +``` +```` + +```ts "apiKey" "fetchDocs" +const apiKey = process.env.DOCS_API_KEY; +await fetchDocs({ apiKey }); +``` + +## Line Numbers + +Code blocks show line numbers by default. Add `noLineNumbers` (or +`showLineNumbers=false`) to hide them for one block. Readers can also show +or hide line numbers with the button in any code block's header. + +````md +```bash noLineNumbers +npm run build +``` +```` + +```bash noLineNumbers +npm run build +``` + Every code block keeps its copy button. The copy button copies the code exactly as written, including diff markers. @@ -1300,6 +1367,12 @@ Inner callout. ::: :::: +`:::details Title` makes a collapsible callout that starts closed. + +:::details Show more +Collapsible callouts work in MDX too. +::: + The `` shortcode renders the same callout. `type` accepts `note`, `tip`, `info`, `warning`, `danger`, `important`, or `caution`. Put the shortcode on its own lines. @@ -1308,6 +1381,60 @@ shortcode on its own lines. Never commit deployment credentials. +## Code Inside Callouts And Tabs + +Code blocks inside callouts and `` get the same copy button and syntax +colours as top-level blocks. + +:::tip Install command + +```bash title="install.sh" +npm install papers +``` + +::: + +## Synced Tabs + +Give `` a `group` key to keep tab sets in sync. Markdown pages use the +same keys (`:::tabs pkg`), so a choice made on one page carries over to the +other. + + + + +```bash +npm run dev +``` + + + + +```bash +pnpm dev +``` + + + + +## Steps And Cards + + + +1. Install the dependencies. +2. Start the dev server. + + + + + + The Markdown syntax for steps and cards. + + + Callouts and code blocks in Markdown. + + + ## GitHub Alerts > [!TIP] @@ -1333,6 +1460,172 @@ and syntax colours. --- +### Steps And Cards + +URL: /docs/developer-guides/steps-and-cards + +--- +description: Numbered steps and link cards for guides and landing pages. +--- + +# Steps And Cards + +Use steps for procedures that readers follow in order, and cards to point +readers to other pages. + +## Steps + +Wrap an ordered list in `:::steps`. Each list item becomes a numbered step. + +```md +:::steps + +1. Install the dependencies. +2. Start the dev server. +3. Open the local URL. + ::: +``` + +:::steps + +1. Install the dependencies. + + ```bash + npm install + ``` + +2. Start the dev server. + + ```bash + npm run dev + ``` + +3. Open the URL that the dev server prints. + +::: + +## Cards + +Put cards in `:::cards`. Start each card with a `::card Title` line. To link +the card, write the title as a Markdown link. Links must be site paths that +start with `/` or `https://` URLs. Other links are dropped and the title shows +as plain text. + +```md +:::cards +::card [Callouts and code](/docs/developer-guides/callouts-and-code) +Admonitions, code titles, and diffs. +::card [MDX authoring](/docs/developer-guides/callouts-in-mdx) +The same features in `.mdx` pages. +::: +``` + +:::cards +::card [Callouts and code](/docs/developer-guides/callouts-and-code) +Admonitions, code titles, and diffs. +::card [MDX authoring](/docs/developer-guides/callouts-in-mdx) +The same features in `.mdx` pages. +::: + +## In MDX + +MDX pages use ``, ``, and ``: + +```mdx + + +1. Install the dependencies. +2. Start the dev server. + + + + + + Admonitions, code titles, and diffs. + + +``` + + +--- + + +### Translating The Interface + +URL: /docs/developer-guides/translating-the-interface + +--- +description: Translate or rename the labels in the docs chrome, per locale. +--- + +# Translating The Interface + +Page content is translated by adding locale folders. The labels around the +content, such as **Previous**, **Next**, **Last updated**, and the copy button, +come from `shared/uiStrings.js`. You can translate or rename them per locale. + +## Set Labels For A Locale + +Add the labels to `uiStringsConfig` in `shared/documentation-config.js`. Use +the locale codes from `i18nConfig.locales`. Labels that you leave out keep the +English default. + +```js title="shared/documentation-config.js" +export const i18nConfig = { + enabled: true, + defaultLocale: 'en', + locales: ['en', 'fr'], + labels: { en: 'EN', fr: 'FR' }, +}; + +export const uiStringsConfig = { + fr: { + previous: 'Précédent', + next: 'Suivant', + lastUpdated: 'Mis à jour le', + searchDocs: 'Rechercher', + copyCode: 'Copier', + }, +}; +``` + +To rename a label on a site with one language, add an entry for the default +locale, for example `en: { edit: 'Edit this page' }`. + +The page language comes from the URL. Dates in the **Last updated** line use +the same locale. + +## Available Labels + +| Key | Default | +| ------------------- | -------------------------------------------- | +| `previous` | Previous | +| `next` | Next | +| `adjacentPages` | Adjacent documentation pages (screen reader) | +| `lastUpdated` | Last updated | +| `edit` | edit | +| `issue` | issue | +| `source` | source | +| `pageSourceActions` | Page source actions (screen reader) | +| `searchDocs` | Search docs | +| `searchPlaceholder` | Search documentation or type a command... | +| `noResults` | No results found for | +| `copyCode` | Copy to clipboard | +| `copied` | Copied | +| `showLineNumbers` | Show line numbers | +| `hideLineNumbers` | Hide line numbers | +| `referencedBy` | Referenced by | + +## Validation + +`npm run generate:docs` (and so `npm run build`) fails if `uiStringsConfig` +uses a locale that is not in `i18nConfig.locales`, an unknown key, or an empty +value. This keeps a typo from hiding a label. + + +--- + + ### Math And Diagrams URL: /docs/developer-guides/math-and-diagrams diff --git a/public/llms.txt b/public/llms.txt index 1eccf9d..e0e47ba 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -31,6 +31,8 @@ A minimal docs starter with install-ready defaults, generated content, and stati - [Code Examples](/docs/developer-guides/code-examples): This page shows the main authoring patterns supported by the current runtime. - [Callouts And Code Blocks](/docs/developer-guides/callouts-and-code): Callouts (admonitions), GitHub-style alerts, code block titles, line highlighting, and diffs. - [Callouts And Code In MDX](/docs/developer-guides/callouts-in-mdx): Use callouts, GitHub alerts, and code fence titles, line highlights, and diffs in MDX pages. +- [Steps And Cards](/docs/developer-guides/steps-and-cards): Numbered steps and link cards for guides and landing pages. +- [Translating The Interface](/docs/developer-guides/translating-the-interface): Translate or rename the labels in the docs chrome, per locale. - [Math And Diagrams](/docs/developer-guides/math-and-diagrams): Papers typesets TeX with KaTeX and renders Mermaid diagrams, including diagrams whose labels contain math. KaTeX loads only on pages that contain math. - [Math And Diagrams In MDX](/docs/developer-guides/math-in-mdx): KaTeX math and Mermaid diagrams in .mdx pages, alongside MDX shortcodes. - [Tailwind Integration](/docs/developer-guides/tailwind-integration): Papers is built with Tailwind CSS 4 (@tailwindcss/vite). This page explains the CSS contract so a downstream Tailwind 4 site can embed or restyle Papers... diff --git a/scripts/generate-docs.mjs b/scripts/generate-docs.mjs index f343a95..845d190 100644 --- a/scripts/generate-docs.mjs +++ b/scripts/generate-docs.mjs @@ -11,7 +11,13 @@ import { getCollectionVariants, validateContentCollections, } from '../shared/content-collections.js'; -import { frameworkDocPaths, openapiConfig } from '../shared/documentation-config.js'; +import { + frameworkDocPaths, + i18nConfig, + openapiConfig, + uiStringsConfig, +} from '../shared/documentation-config.js'; +import { validateUiStringsConfig } from '../shared/uiStrings.js'; import { serializeArtifactJson } from './lib/docsArtifacts.mjs'; import { collectDocumentPaths, createGeneratedDocsArtifacts } from './lib/docsGeneration.mjs'; import { @@ -25,6 +31,11 @@ import { resolveCollectionFrameworkContentRoot, } from './lib/collectionContentRoot.mjs'; +const uiStringProblems = validateUiStringsConfig(uiStringsConfig, i18nConfig.locales || []); +if (uiStringProblems.length > 0) { + throw new Error(`Invalid uiStringsConfig:\n- ${uiStringProblems.join('\n- ')}`); +} + async function readExistingJson(filePath) { try { return JSON.parse(await readFile(filePath, 'utf8')); diff --git a/scripts/lib/mdxAuthoring.mjs b/scripts/lib/mdxAuthoring.mjs index 9d5dac3..0b4d6c8 100644 --- a/scripts/lib/mdxAuthoring.mjs +++ b/scripts/lib/mdxAuthoring.mjs @@ -4,9 +4,9 @@ * (`title="..."`, `{1,3-5}`, `diff`). Output uses the same `admonition-*` * classes as Markdown pages and passes through the MDX sanitizer. */ -import { getDiffLineKind, parseCodeFenceMeta } from '../../shared/codeFence.js'; +import { getDiffLineKind, parseCodeFenceMeta, parseDiffLanguage } from '../../shared/codeFence.js'; -export const ADMONITION_TYPES = ['note', 'tip', 'info', 'warning', 'danger']; +export const ADMONITION_TYPES = ['note', 'tip', 'info', 'warning', 'danger', 'details']; const DEFAULT_TITLES = { note: 'Note', @@ -14,6 +14,7 @@ const DEFAULT_TITLES = { info: 'Info', warning: 'Warning', danger: 'Danger', + details: 'Details', important: 'Important', caution: 'Caution', }; @@ -40,6 +41,29 @@ function titleNode(title) { /** Build an mdast node that renders as the shared admonition markup. */ export function createAdmonitionNode(type, title, children, extraClassName = '') { + if (type === 'details') { + return { + type: 'admonition', + data: { + hName: 'details', + hProperties: { + className: `admonition admonition-details${extraClassName ? ` ${extraClassName}` : ''}`, + }, + }, + children: [ + { + type: 'admonitionTitle', + data: { hName: 'summary', hProperties: { className: 'admonition-title' } }, + children: [{ type: 'text', value: title }], + }, + { + type: 'admonitionContent', + data: { hName: 'div', hProperties: { className: 'admonition-content' } }, + children, + }, + ], + }; + } return { type: 'admonition', data: { @@ -61,7 +85,8 @@ export function createAdmonitionNode(type, title, children, extraClassName = '') }; } -const OPENER_PATTERN = /^(:{3,})[ \t]*(note|tip|info|warning|danger)\b[ \t]*([^\n]*)(?:\n|$)/i; +const OPENER_PATTERN = + /^(:{3,})[ \t]*(note|tip|info|warning|danger|details)\b[ \t]*([^\n]*)(?:\n|$)/i; function firstText(node) { return node?.type === 'paragraph' && node.children?.[0]?.type === 'text' @@ -176,8 +201,16 @@ export function remarkAdmonitionDirectives() { function transformCodeNode(node) { const meta = parseCodeFenceMeta(node.meta || ''); const language = node.lang || 'text'; - const isDiff = language.toLowerCase() === 'diff'; - if (!meta.title && !meta.highlightLines && !isDiff) return null; + const { isDiff } = parseDiffLanguage(language); + if ( + !meta.title && + !meta.highlightLines && + !meta.highlightWords && + meta.showLineNumbers === undefined && + !isDiff + ) { + return null; + } const highlighted = new Set(meta.highlightLines || []); const lines = String(node.value || '').split('\n'); @@ -222,7 +255,19 @@ function transformCodeNode(node) { return { type: 'codeFigure', - data: { hName: 'div', hProperties: { className: 'papers-code', dataCodeLanguage: language } }, + data: { + hName: 'div', + hProperties: { + className: 'papers-code', + dataCodeLanguage: language, + ...(meta.highlightWords + ? { dataHighlightWords: encodeURIComponent(JSON.stringify(meta.highlightWords)) } + : {}), + ...(meta.showLineNumbers !== undefined + ? { dataLineNumbers: String(meta.showLineNumbers) } + : {}), + }, + }, children, }; } diff --git a/scripts/lib/mdxCompile.mjs b/scripts/lib/mdxCompile.mjs index de8f172..7737e3c 100644 --- a/scripts/lib/mdxCompile.mjs +++ b/scripts/lib/mdxCompile.mjs @@ -25,11 +25,14 @@ const sanitizeSchema = { ...(defaultSchema.attributes?.div || []), [ 'className', - /^(papers-(callout|tabs|card|character-note|code)|admonition|formula|math-display|mermaid-placeholder)/, + /^(papers-(callout|tabs|card|character-note|code|steps)|admonition|formula|math-display|mermaid-placeholder)/, ], 'ariaLabel', 'dataCodeLanguage', 'dataCodeTitle', + 'dataHighlightWords', + 'dataTabsGroup', + 'dataLineNumbers', 'dataCallout', 'dataMermaidChart', 'dataMathTex', @@ -46,8 +49,15 @@ const sanitizeSchema = { 'loading', 'decoding', ], - details: [...(defaultSchema.attributes?.details || []), ['className', /^papers-tabs/], 'open'], - summary: [...(defaultSchema.attributes?.summary || []), ['className', /^papers-tabs/]], + details: [ + ...(defaultSchema.attributes?.details || []), + ['className', /^(papers-tabs|admonition)/], + 'open', + ], + summary: [ + ...(defaultSchema.attributes?.summary || []), + ['className', /^(papers-tabs|admonition-title)/], + ], // Math placeholders, typeset client-side by KaTeX (see src/utils/renderMath.ts). span: [ ...(defaultSchema.attributes?.span || []), @@ -60,7 +70,10 @@ const sanitizeSchema = { ...(defaultSchema.attributes?.p || []), ['className', /^(papers-(callout|card|character-note)|admonition-title)/], ], - a: [...(defaultSchema.attributes?.a || []), ['target', 'rel']], + a: [ + ...(defaultSchema.attributes?.a || []), + ['target', 'rel'], + ], }, tagNames: [...(defaultSchema.tagNames || []), 'details', 'summary'], }; diff --git a/scripts/lib/mdxShortcodes.mjs b/scripts/lib/mdxShortcodes.mjs index 74f4d59..3f28abf 100644 --- a/scripts/lib/mdxShortcodes.mjs +++ b/scripts/lib/mdxShortcodes.mjs @@ -1,3 +1,4 @@ +import { sanitizeCardHref } from '../../shared/codeFence.js'; import { createAdmonitionNode, defaultAdmonitionTitle, toAdmonitionType } from './mdxAuthoring.mjs'; import { characterNoteAvatars } from '../../shared/character-note-config.js'; import { MDX_COMPONENT_REGISTRY, normalizeMdxComponentNames } from '../../shared/mdxComponents.js'; @@ -186,10 +187,13 @@ function transformTabs(node) { const tabNodes = collectTabNodes(node.children); const panels = tabNodes.map((tab) => transformTab(tab)); + const group = (readJsxAttribute(node.attributes, 'group') || '').trim().toLowerCase(); return createElement( 'div', { className: 'papers-tabs', + // Same key format as Markdown `:::tabs `; invalid keys are dropped. + ...(/^[a-z0-9][a-z0-9_-]{0,39}$/.test(group) ? { dataTabsGroup: group } : {}), }, panels ); @@ -252,18 +256,20 @@ function transformCharacterNote(node) { function transformCard(node) { const title = readJsxAttribute(node.attributes, 'title'); + const href = sanitizeCardHref(readJsxAttribute(node.attributes, 'href')); const children = [...normalizeChildren(node.children)]; if (title) { + const titleText = { type: 'text', value: title }; children.unshift({ type: 'paragraph', data: { hName: 'p', hProperties: { - className: 'papers-card__title', + className: href ? 'papers-card__title papers-card__title--link' : 'papers-card__title', }, }, - children: [{ type: 'text', value: title }], + children: href ? [{ type: 'link', url: href, children: [titleText] }] : [titleText], }); } @@ -276,6 +282,16 @@ function transformCard(node) { ); } +function transformCardGrid(node) { + return createElement('div', { className: 'papers-card-grid' }, [ + ...normalizeChildren(node.children), + ]); +} + +function transformSteps(node) { + return createElement('div', { className: 'papers-steps' }, [...normalizeChildren(node.children)]); +} + function transformShortcode(node) { if (node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement') { return null; @@ -294,6 +310,10 @@ function transformShortcode(node) { return transformTab(node); case 'Card': return transformCard(node); + case 'CardGrid': + return transformCardGrid(node); + case 'Steps': + return transformSteps(node); case 'CharacterNote': return transformCharacterNote(node); default: @@ -315,7 +335,7 @@ function visitChildren(node, visitor) { }); } -const BLOCK_SHORTCODES = new Set(['Callout', 'Tabs', 'Card', 'CharacterNote']); +const BLOCK_SHORTCODES = new Set(['Callout', 'Tabs', 'Card', 'CardGrid', 'Steps', 'CharacterNote']); /** * `text` written on one line parses as a text element inside a @@ -327,25 +347,29 @@ export function hoistInlineBlockShortcodes(node) { return; } - node.children = node.children.map((child) => { + node.children = node.children.flatMap((child) => { if (child.type === 'paragraph') { const meaningful = child.children.filter( (leaf) => !(leaf.type === 'text' && leaf.value.trim() === '') ); - const [only] = meaningful; + // A paragraph holding only block shortcodes (e.g. several s on + // adjacent lines) becomes those blocks. if ( - meaningful.length === 1 && - only.type === 'mdxJsxTextElement' && - BLOCK_SHORTCODES.has(only.name) + meaningful.length > 0 && + meaningful.every( + (leaf) => leaf.type === 'mdxJsxTextElement' && BLOCK_SHORTCODES.has(leaf.name) + ) ) { - const hoisted = { ...only, type: 'mdxJsxFlowElement' }; - hoistInlineBlockShortcodes(hoisted); - return hoisted; + return meaningful.map((leaf) => { + const hoisted = { ...leaf, type: 'mdxJsxFlowElement' }; + hoistInlineBlockShortcodes(hoisted); + return hoisted; + }); } - return child; + return [child]; } hoistInlineBlockShortcodes(child); - return child; + return [child]; }); } diff --git a/scripts/lib/pagefindArtifacts.mjs b/scripts/lib/pagefindArtifacts.mjs index 103e7f1..62b3420 100644 --- a/scripts/lib/pagefindArtifacts.mjs +++ b/scripts/lib/pagefindArtifacts.mjs @@ -66,6 +66,14 @@ function createDocumentationRecord(collection, context, docPath, document, index content, language: context.locale || 'en', meta, + // Pagefind filters: the client limits results to the reader's version + // and locale and supports `docs:` / `api:` scopes (src/lib/searchRanking.ts). + filters: { + section: ['docs'], + collection: [collection.id], + ...(context.version ? { version: [context.version] } : {}), + ...(context.locale ? { locale: [context.locale] } : {}), + }, }, }; } @@ -100,6 +108,7 @@ function createOpenApiRecords(openapiArtifacts) { title: operation.summary || `${operation.method} ${operation.path}`, kind: 'openapi-operation', }, + filters: { section: ['api'] }, }, }); } @@ -119,6 +128,7 @@ function createOpenApiRecords(openapiArtifacts) { .join('\n'), language: 'en', meta: { title: model.name, kind: 'openapi-model' }, + filters: { section: ['api'] }, }, }); } diff --git a/shared/codeFence.js b/shared/codeFence.js index 6e893c7..81052d6 100644 --- a/shared/codeFence.js +++ b/shared/codeFence.js @@ -5,6 +5,7 @@ */ const MAX_HIGHLIGHT_LINE = 10000; +const MAX_HIGHLIGHT_WORDS = 10; /** Parse `1,3-5` line ranges. Invalid, zero, reversed, or huge ranges are ignored. */ export function parseHighlightLines(spec) { @@ -32,6 +33,21 @@ export function parseCodeFenceMeta(meta) { rest = rest.replace(titleMatch[0], ' '); } + // Quoted terms such as "token" or 'apiKey' highlight every occurrence. + const words = []; + rest = rest.replace(/"([^"\n]{1,80})"|'([^'\n]{1,80})'/g, (_match, double, single) => { + const word = (double ?? single ?? '').trim(); + if (word && !words.includes(word) && words.length < MAX_HIGHLIGHT_WORDS) words.push(word); + return ' '; + }); + if (words.length > 0) result.highlightWords = words; + + if (/(?:^|\s)(?:showLineNumbers=false|noLineNumbers)(?=\s|$)/.test(rest)) { + result.showLineNumbers = false; + } else if (/(?:^|\s)showLineNumbers(?:=true)?(?=\s|$)/.test(rest)) { + result.showLineNumbers = true; + } + const rangeMatch = /\{([^}]*)\}/.exec(rest); if (rangeMatch) { const lines = parseHighlightLines(rangeMatch[1]); @@ -48,6 +64,19 @@ export function parseCodeFenceInfo(info) { return { language, ...parseCodeFenceMeta(trimmed.slice(language.length)) }; } +/** + * `diff` fences mark +/- lines; `diff-ts` (any `diff-`) also colours + * the code with that language. Returns the language used for syntax colours. + */ +export function parseDiffLanguage(language) { + const value = String(language || '').toLowerCase(); + if (value === 'diff') return { isDiff: true, syntaxLanguage: 'diff' }; + if (value.startsWith('diff-') && value.length > 5) { + return { isDiff: true, syntaxLanguage: value.slice(5) }; + } + return { isDiff: false, syntaxLanguage: value }; +} + /** Classify a diff line by its leading marker; non-diff blocks never mark lines. */ export function getDiffLineKind(line, isDiff) { if (!isDiff) return null; @@ -55,3 +84,14 @@ export function getDiffLineKind(line, isDiff) { if (line.startsWith('-') && !line.startsWith('---')) return 'removed'; return null; } + +/** + * Card links accept site-relative paths (`/docs/...`) and https URLs only. + * Anything else (protocol-relative, javascript:, data:) is dropped. + */ +export function sanitizeCardHref(href) { + const value = String(href || '').trim(); + if (/^\/(?!\/)[^\s"'<>]*$/.test(value)) return value; + if (/^https:\/\/[^\s"'<>]+$/i.test(value)) return value; + return null; +} diff --git a/shared/documentation-config.js b/shared/documentation-config.js index 98626e5..dd4b951 100644 --- a/shared/documentation-config.js +++ b/shared/documentation-config.js @@ -58,6 +58,14 @@ export const i18nConfig = { }, }; +/** + * Interface label overrides per locale (keys from shared/uiStrings.js). Any + * label you leave out keeps its English default. Example: + * fr: { previous: 'Précédent', next: 'Suivant', lastUpdated: 'Mis à jour le' } + * @type {Record>} + */ +export const uiStringsConfig = {}; + /** * Site chrome — language switcher placement + sidebar skin. * @type {{ @@ -343,6 +351,18 @@ export const documentationTree = [ path: 'developer-guides/callouts-in-mdx', tags: ['callouts', 'admonitions', 'code', 'mdx'], }, + { + type: 'file', + name: 'Steps And Cards.md', + path: 'developer-guides/steps-and-cards', + tags: ['steps', 'cards', 'components', 'layout'], + }, + { + type: 'file', + name: 'Translating The Interface.md', + path: 'developer-guides/translating-the-interface', + tags: ['i18n', 'locales', 'labels', 'translation'], + }, { type: 'file', name: 'Math And Diagrams.md', diff --git a/shared/mdxComponents.js b/shared/mdxComponents.js index 50751a6..885be32 100644 --- a/shared/mdxComponents.js +++ b/shared/mdxComponents.js @@ -12,7 +12,7 @@ export const MDX_COMPONENT_REGISTRY = Object.freeze({ output: 'sanitized callout div', }), Tabs: Object.freeze({ - allowedProps: Object.freeze([]), + allowedProps: Object.freeze(['group']), output: 'sanitized details group', }), Tab: Object.freeze({ @@ -20,9 +20,17 @@ export const MDX_COMPONENT_REGISTRY = Object.freeze({ output: 'sanitized details panel', }), Card: Object.freeze({ - allowedProps: Object.freeze(['title']), + allowedProps: Object.freeze(['title', 'href']), output: 'sanitized card div', }), + CardGrid: Object.freeze({ + allowedProps: Object.freeze([]), + output: 'sanitized card grid div', + }), + Steps: Object.freeze({ + allowedProps: Object.freeze([]), + output: 'sanitized numbered steps div', + }), CharacterNote: Object.freeze({ allowedProps: Object.freeze(['type', 'title']), output: 'sanitized character note div', diff --git a/shared/uiStrings.js b/shared/uiStrings.js new file mode 100644 index 0000000..d29c000 --- /dev/null +++ b/shared/uiStrings.js @@ -0,0 +1,57 @@ +/** + * Interface labels for the docs chrome (pager, footer, search, code blocks). + * English defaults live here; translate or rename them per locale with + * `uiStringsConfig` in shared/documentation-config.js. + */ +export const DEFAULT_UI_STRINGS = Object.freeze({ + previous: 'Previous', + next: 'Next', + adjacentPages: 'Adjacent documentation pages', + lastUpdated: 'Last updated', + edit: 'edit', + issue: 'issue', + source: 'source', + pageSourceActions: 'Page source actions', + searchDocs: 'Search docs', + searchPlaceholder: 'Search documentation or type a command...', + noResults: 'No results found for', + copyCode: 'Copy to clipboard', + copied: 'Copied', + showLineNumbers: 'Show line numbers', + hideLineNumbers: 'Hide line numbers', + referencedBy: 'Referenced by', +}); + +export const UI_STRING_KEYS = Object.freeze(Object.keys(DEFAULT_UI_STRINGS)); + +/** + * Merge the overrides for `locale` over the defaults. Unknown keys and + * non-string or empty values are ignored, so a typo never blanks a label. + */ +export function resolveUiStrings(locale, overridesByLocale = {}) { + const overrides = (locale && overridesByLocale?.[locale]) || {}; + const resolved = { ...DEFAULT_UI_STRINGS }; + for (const key of UI_STRING_KEYS) { + const value = overrides[key]; + if (typeof value === 'string' && value.trim()) resolved[key] = value; + } + return Object.freeze(resolved); +} + +/** Return config problems as messages (unknown locales, keys, or bad values). */ +export function validateUiStringsConfig(config, locales = []) { + const problems = []; + for (const [locale, overrides] of Object.entries(config || {})) { + if (locales.length > 0 && !locales.includes(locale)) { + problems.push(`uiStringsConfig.${locale}: locale is not in i18nConfig.locales`); + } + for (const [key, value] of Object.entries(overrides || {})) { + if (!UI_STRING_KEYS.includes(key)) { + problems.push(`uiStringsConfig.${locale}.${key}: unknown label`); + } else if (typeof value !== 'string' || !value.trim()) { + problems.push(`uiStringsConfig.${locale}.${key}: must be a non-empty string`); + } + } + } + return problems; +} diff --git a/src/components/CodeBlock.module.css b/src/components/CodeBlock.module.css index eede9a7..e90fe49 100644 --- a/src/components/CodeBlock.module.css +++ b/src/components/CodeBlock.module.css @@ -87,6 +87,10 @@ overflow-wrap: anywhere; } + .codeBlockCopyBtn + .codeBlockCopyBtn { + margin-left: 0.25rem; + } + .codeBlockFilename + .codeBlockLanguage { margin-right: 0.75rem; } @@ -112,6 +116,14 @@ border-left-color: rgb(214, 60, 60); } + .codeBlockContent :global(.code-word-highlight) { + padding: 0 0.15em; + border-radius: 3px; + background-color: rgba(var(--primary-color-rgb), 0.22); + color: inherit; + outline: 1px solid rgba(var(--primary-color-rgb), 0.45); + } + .codeBlockCopyBtn { display: flex; align-items: center; diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx index 125d8ab..ade068b 100644 --- a/src/components/CodeBlock.tsx +++ b/src/components/CodeBlock.tsx @@ -9,9 +9,11 @@ import React, { import { ANIMATION_DURATION } from '../constants/ui'; import { getLanguageDisplay } from '../utils/languageUtils'; -import { getDiffLineKind } from '../utils/markdownCore'; +import { getDiffLineKind, parseDiffLanguage } from '../utils/markdownCore'; +import { createWordPattern, markWords, type HighlighterNode } from '../utils/codeWords'; import styles from './CodeBlock.module.css'; +import { useUiStrings } from '../lib/uiStrings'; interface CodeSnippet { language: string; @@ -19,6 +21,8 @@ interface CodeSnippet { label?: string; title?: string; highlightLines?: number[]; + highlightWords?: string[]; + showLineNumbers?: boolean; } interface CodeBlockProps { @@ -35,6 +39,11 @@ type SyntaxHighlighterProps = { showLineNumbers?: boolean; wrapLines?: boolean; wrapLongLines?: boolean; + renderer?: (props: { + rows: HighlighterNode[]; + stylesheet: Record; + useInlineStyles: boolean; + }) => React.ReactNode; lineProps?: (lineNumber: number) => { className?: string; style?: CSSProperties; @@ -51,8 +60,16 @@ type SyntaxHighlighterComponent = ComponentType & { registerLanguage: (name: string, language: unknown) => void; }; +type CreateElement = (props: { + node: HighlighterNode; + stylesheet: Record; + useInlineStyles: boolean; + key: string; +}) => React.ReactNode; + type SyntaxModule = { SyntaxHighlighter: SyntaxHighlighterComponent; + createElement: CreateElement; }; const REGISTERED_LANGUAGES = new Set([ @@ -64,10 +81,21 @@ const REGISTERED_LANGUAGES = new Set([ 'markup', 'tsx', 'typescript', + 'diff', + 'python', + 'yaml', + 'sql', + 'go', + 'rust', ]); const LANGUAGE_ALIASES: Record = { env: 'bash', + py: 'python', + yml: 'yaml', + rs: 'rust', + sh: 'bash', + shell: 'bash', html: 'markup', js: 'javascript', md: 'markdown', @@ -155,7 +183,10 @@ const minimalSyntaxTheme: Record = { color: 'var(--code-text-color)', }, inserted: { - color: 'var(--code-text-color)', + color: 'var(--code-inserted-color, rgb(34, 140, 80))', + }, + deleted: { + color: 'var(--code-deleted-color, rgb(200, 60, 60))', }, 'attr-name': { color: 'var(--code-subtle-color)', @@ -184,9 +215,6 @@ const minimalSyntaxTheme: Record = { regex: { color: 'var(--code-subtle-color)', }, - deleted: { - color: 'var(--code-subtle-color)', - }, bold: { fontWeight: 700, }, @@ -213,6 +241,9 @@ const CodeBlock: React.FC = React.memo( ({ snippets = [], title, defaultLanguage, showLineNumbers = true, className = '' }) => { const [activeTab, setActiveTab] = useState(0); const [copied, setCopied] = useState(false); + const ui = useUiStrings(); + // Per-block line-number toggle; null follows the fence/default setting. + const [lineNumberOverride, setLineNumberOverride] = useState(null); const [mounted, setMounted] = useState(false); const [syntaxModule, setSyntaxModule] = useState(null); @@ -234,6 +265,13 @@ const CodeBlock: React.FC = React.memo( markup, tsx, typescript, + diff, + python, + yaml, + sql, + go, + rust, + createElementModule, ] = await Promise.all([ import('react-syntax-highlighter/dist/esm/prism-light'), import('react-syntax-highlighter/dist/esm/languages/prism/bash'), @@ -244,6 +282,13 @@ const CodeBlock: React.FC = React.memo( import('react-syntax-highlighter/dist/esm/languages/prism/markup'), import('react-syntax-highlighter/dist/esm/languages/prism/tsx'), import('react-syntax-highlighter/dist/esm/languages/prism/typescript'), + import('react-syntax-highlighter/dist/esm/languages/prism/diff'), + import('react-syntax-highlighter/dist/esm/languages/prism/python'), + import('react-syntax-highlighter/dist/esm/languages/prism/yaml'), + import('react-syntax-highlighter/dist/esm/languages/prism/sql'), + import('react-syntax-highlighter/dist/esm/languages/prism/go'), + import('react-syntax-highlighter/dist/esm/languages/prism/rust'), + import('react-syntax-highlighter/dist/esm/create-element'), ]); const SyntaxHighlighter = PrismLight as SyntaxHighlighterComponent; @@ -255,9 +300,18 @@ const CodeBlock: React.FC = React.memo( SyntaxHighlighter.registerLanguage('markup', markup.default); SyntaxHighlighter.registerLanguage('tsx', tsx.default); SyntaxHighlighter.registerLanguage('typescript', typescript.default); + SyntaxHighlighter.registerLanguage('diff', diff.default); + SyntaxHighlighter.registerLanguage('python', python.default); + SyntaxHighlighter.registerLanguage('yaml', yaml.default); + SyntaxHighlighter.registerLanguage('sql', sql.default); + SyntaxHighlighter.registerLanguage('go', go.default); + SyntaxHighlighter.registerLanguage('rust', rust.default); if (!cancelled) { - setSyntaxModule({ SyntaxHighlighter }); + setSyntaxModule({ + SyntaxHighlighter, + createElement: createElementModule.default as unknown as CreateElement, + }); } } @@ -318,14 +372,37 @@ const CodeBlock: React.FC = React.memo( } const { SyntaxHighlighter } = syntaxModule; - const normalizedLanguage = normalizeLanguage(currentSnippet.language); + const { isDiff, syntaxLanguage } = parseDiffLanguage(currentSnippet.language); + const normalizedLanguage = normalizeLanguage(syntaxLanguage); const snippetTitle = title ?? currentSnippet.title; const trimmedCode = currentSnippet.code.trim(); - const isDiff = currentSnippet.language.toLowerCase() === 'diff'; const codeLines = trimmedCode.split('\n'); const highlighted = new Set(currentSnippet.highlightLines ?? []); // Only wrap lines when a block uses line metadata, so plain blocks render exactly as before. const needsLineProps = isDiff || highlighted.size > 0; + const wordPattern = createWordPattern(currentSnippet.highlightWords ?? []); + const lineNumbersVisible = + lineNumberOverride ?? currentSnippet.showLineNumbers ?? showLineNumbers; + const { createElement: createHighlighterElement } = syntaxModule; + const wordRenderer = wordPattern + ? ({ + rows, + stylesheet, + useInlineStyles, + }: { + rows: HighlighterNode[]; + stylesheet: Record; + useInlineStyles: boolean; + }) => + rows.map((row, index) => + createHighlighterElement({ + node: markWords(row, wordPattern), + stylesheet, + useInlineStyles, + key: `code-row-${index}`, + }) + ) + : undefined; const lineProps = (lineNumber: number) => { const classes = [styles.codeLine]; const diffKind = getDiffLineKind(codeLines[lineNumber - 1] ?? '', isDiff); @@ -366,11 +443,22 @@ const CodeBlock: React.FC = React.memo( {currentSnippet.label || getLanguageDisplay(currentSnippet.language)} +