@writedocs/generator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +17 -0
  3. package/astro.config.mjs +419 -0
  4. package/bin/writedocs.js +73 -0
  5. package/package.json +79 -0
  6. package/src/assets/wd_watermark.png +0 -0
  7. package/src/assets/wd_watermark_dark.png +0 -0
  8. package/src/cli/build-auth.js +53 -0
  9. package/src/cli/build.js +40 -0
  10. package/src/cli/dev.js +12 -0
  11. package/src/cli/generate-api-pages.js +359 -0
  12. package/src/cli/init.js +81 -0
  13. package/src/cli/preflight.js +40 -0
  14. package/src/cli/run-astro.js +57 -0
  15. package/src/cli/run-pagefind.js +66 -0
  16. package/src/cli/write-redirects-file.js +80 -0
  17. package/src/components/Accordion.astro +164 -0
  18. package/src/components/AccordionGroup.astro +40 -0
  19. package/src/components/ApiLangSelect.astro +168 -0
  20. package/src/components/ApiPlayground.astro +281 -0
  21. package/src/components/ApiReferencePanel.astro +1754 -0
  22. package/src/components/ApiSchemaField.astro +54 -0
  23. package/src/components/AppIcon.astro +32 -0
  24. package/src/components/Badge.astro +128 -0
  25. package/src/components/Callout.astro +168 -0
  26. package/src/components/Card.astro +136 -0
  27. package/src/components/CardGroup.astro +20 -0
  28. package/src/components/CodeGroup.astro +184 -0
  29. package/src/components/CopyPageMenu.astro +246 -0
  30. package/src/components/Danger.astro +12 -0
  31. package/src/components/Expandable.astro +126 -0
  32. package/src/components/Frame.astro +102 -0
  33. package/src/components/Hint.astro +99 -0
  34. package/src/components/Icon.astro +70 -0
  35. package/src/components/Image.astro +147 -0
  36. package/src/components/Info.astro +12 -0
  37. package/src/components/Note.astro +12 -0
  38. package/src/components/Parameter.astro +119 -0
  39. package/src/components/RequestExample.astro +33 -0
  40. package/src/components/ResponseExample.astro +19 -0
  41. package/src/components/Searchbar.astro +117 -0
  42. package/src/components/Step.astro +10 -0
  43. package/src/components/Steps.astro +32 -0
  44. package/src/components/Tab.astro +9 -0
  45. package/src/components/Tabs.astro +52 -0
  46. package/src/components/Tip.astro +12 -0
  47. package/src/components/Video.astro +135 -0
  48. package/src/components/Warning.astro +12 -0
  49. package/src/components/index.ts +48 -0
  50. package/src/content.config.ts +223 -0
  51. package/src/layout/BaseLayout.astro +750 -0
  52. package/src/layout/components/AnalyticsScripts.astro +77 -0
  53. package/src/layout/components/AskAiWidget.astro +37 -0
  54. package/src/layout/components/Breadcrumbs.astro +97 -0
  55. package/src/layout/components/ImageZoom.astro +19 -0
  56. package/src/layout/components/MobileMenu.astro +200 -0
  57. package/src/layout/components/NavTree.astro +351 -0
  58. package/src/layout/components/SearchModal.astro +42 -0
  59. package/src/layout/components/Sidebar.astro +122 -0
  60. package/src/layout/components/SiteFooter.astro +85 -0
  61. package/src/layout/components/TableOfContents.astro +117 -0
  62. package/src/layout/components/TopBar.astro +311 -0
  63. package/src/layout/styles/banner.css +44 -0
  64. package/src/layout/styles/base.css +234 -0
  65. package/src/layout/styles/dropdown.css +133 -0
  66. package/src/layout/styles/footer.css +108 -0
  67. package/src/layout/styles/image-zoom.css +50 -0
  68. package/src/layout/styles/mobile-menu.css +258 -0
  69. package/src/layout/styles/search-modal.css +122 -0
  70. package/src/layout/styles/topbar.css +437 -0
  71. package/src/lib/config.ts +2131 -0
  72. package/src/lib/mdx-auto-hydrate.js +70 -0
  73. package/src/lib/mdx-inject-builtins.js +87 -0
  74. package/src/lib/mdx-substitute-variables.js +66 -0
  75. package/src/lib/mdx-title-anchor-ids.js +84 -0
  76. package/src/lib/mermaid-rehype.js +72 -0
  77. package/src/lib/openapi-render.ts +479 -0
  78. package/src/lib/shiki-code-block.js +102 -0
  79. package/src/lib/shiki-copy-button.js +45 -0
  80. package/src/lib/styles-asset-integration.js +210 -0
  81. package/src/lib/writedocs-temp-dir.js +93 -0
  82. package/src/pages/404.astro +62 -0
  83. package/src/pages/[...slug].astro +1270 -0
  84. package/src/pages/[...slug].md.ts +78 -0
  85. package/src/pages/llms-full.txt.ts +71 -0
  86. package/src/pages/llms.txt.ts +141 -0
  87. package/src/scripts/banner.ts +20 -0
  88. package/src/scripts/dropdowns.ts +61 -0
  89. package/src/scripts/image-zoom.ts +66 -0
  90. package/src/scripts/mobile-menu.ts +55 -0
  91. package/src/scripts/search.ts +155 -0
  92. package/src/scripts/sidebar-scroll.ts +65 -0
  93. package/src/scripts/theme-toggle.ts +35 -0
  94. package/src/scripts/topbar-offset.ts +141 -0
  95. package/src/styles/global.css +18 -0
@@ -0,0 +1,70 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ const CLIENT_ATTR_PREFIX = 'client:';
4
+ const REACT_SNIPPET_EXTENSION = /\.(jsx|tsx)$/i;
5
+
6
+ /**
7
+ * A remark plugin (registered via `mdx({ remarkPlugins: [...] })` in
8
+ * astro.config.mjs) that auto-adds `client:load` to every JSX usage of a
9
+ * component imported from a `.jsx`/`.tsx` file, unless that usage already
10
+ * has its own `client:*` attribute.
11
+ *
12
+ * Why this exists: Astro only ships a framework component's JS (and
13
+ * hydrates it in the browser) when the JSX using it carries an explicit
14
+ * `client:*` directive - with none, it still server-renders fine, but
15
+ * stays inert (no event handlers ever attach). That's the right default
16
+ * for Astro's own `.astro` components generally, but it means every React
17
+ * snippet with a hook would silently do nothing unless whoever *used* the
18
+ * snippet - not whoever *wrote* it - remembered to add `client:load` (or
19
+ * `client:visible`/`client:idle`) at each call site. Requiring every
20
+ * import site to know and repeat an Astro-specific detail isn't
21
+ * reasonable for a snippet meant to be dropped in and just work - so this
22
+ * makes `client:load` automatic, and an explicit `client:*` attribute (for
23
+ * a different hydration strategy - `client:visible`, `client:idle`,
24
+ * `client:only`) still works exactly as before, since this only adds the
25
+ * attribute when none is already present.
26
+ *
27
+ * Only affects components imported from `.jsx`/`.tsx` - `.astro` and
28
+ * `.mdx` imports (snippets or otherwise) are untouched, since Astro/MDX
29
+ * components have no hydration model to opt into in the first place;
30
+ * adding `client:*` to one is a compiler error, not a no-op.
31
+ */
32
+ export function remarkAutoHydrateSnippets() {
33
+ return (tree) => {
34
+ const reactLocalNames = new Set();
35
+
36
+ visit(tree, 'mdxjsEsm', (node) => {
37
+ const body = node.data?.estree?.body;
38
+ if (!body) return;
39
+ for (const stmt of body) {
40
+ if (stmt.type !== 'ImportDeclaration') continue;
41
+ if (typeof stmt.source?.value !== 'string') continue;
42
+ if (!REACT_SNIPPET_EXTENSION.test(stmt.source.value)) continue;
43
+ for (const specifier of stmt.specifiers) {
44
+ // Default (`import Foo from '...'`) and named
45
+ // (`import { Foo } from '...'`) imports both bind a local JSX-
46
+ // usable identifier. A namespace import (`import * as Foo`)
47
+ // isn't used as a JSX tag by itself (`<Foo.Bar />` wouldn't
48
+ // match this node's `name` anyway - see the mdxJsxFlowElement/
49
+ // mdxJsxTextElement visit below), so it's left alone.
50
+ if (specifier.type === 'ImportDefaultSpecifier' || specifier.type === 'ImportSpecifier') {
51
+ reactLocalNames.add(specifier.local.name);
52
+ }
53
+ }
54
+ }
55
+ });
56
+
57
+ if (reactLocalNames.size === 0) return;
58
+
59
+ visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node) => {
60
+ if (!node.name || !reactLocalNames.has(node.name)) return;
61
+ const attributes = node.attributes ?? [];
62
+ const hasClientDirective = attributes.some(
63
+ (attr) => attr.type === 'mdxJsxAttribute' && typeof attr.name === 'string' && attr.name.startsWith(CLIENT_ATTR_PREFIX)
64
+ );
65
+ if (hasClientDirective) return;
66
+ attributes.push({ type: 'mdxJsxAttribute', name: 'client:load', value: null });
67
+ node.attributes = attributes;
68
+ });
69
+ };
70
+ }
@@ -0,0 +1,87 @@
1
+ import { visit } from 'unist-util-visit';
2
+ import { parse as acornParse } from 'acorn';
3
+
4
+ // The exact same set [...slug].astro passes as the `components` prop to
5
+ // every page's own <Content components={components} /> call (see
6
+ // src/components/index.ts, the barrel this imports from) - kept as one
7
+ // literal list rather than importing that map here, since this runs inside
8
+ // astro.config.mjs's markdown pipeline, a different module graph than
9
+ // [...slug].astro's own.
10
+ const BUILTIN_COMPONENT_NAMES = [
11
+ 'Callout',
12
+ 'Note',
13
+ 'Info',
14
+ 'Tip',
15
+ 'Warning',
16
+ 'Danger',
17
+ 'Card',
18
+ 'CardGroup',
19
+ 'Tabs',
20
+ 'Tab',
21
+ 'CodeGroup',
22
+ 'Accordion',
23
+ 'AccordionGroup',
24
+ 'Steps',
25
+ 'Step',
26
+ 'Hint',
27
+ 'Image',
28
+ 'Frame',
29
+ 'Video',
30
+ 'Parameter',
31
+ 'Expandable',
32
+ 'Searchbar',
33
+ 'Badge',
34
+ 'Icon',
35
+ 'RequestExample',
36
+ 'ResponseExample',
37
+ ];
38
+
39
+ const PACKAGE_SPECIFIER = 'writedocs/components';
40
+
41
+ /**
42
+ * A remark plugin that auto-imports any of Writedocs' built-in components
43
+ * (`Callout`, `Card`, ...) a `.mdx` file uses but never itself imported -
44
+ * from `writedocs/components` (see src/components/index.ts) - so a snippet
45
+ * (see docs/dev/docs/snippets.mdx) gets the exact same zero-import
46
+ * experience a page under docs/ already has via [...slug].astro's own
47
+ * `components` prop. Runs on every `.mdx` file, pages included - harmless
48
+ * there since a page's own `<Callout>` usage already resolves correctly via
49
+ * that prop; the injected import just gives it an equally-correct direct
50
+ * binding instead, and doesn't touch the page's *own* file on disk (this
51
+ * only ever mutates the in-memory AST used for rendering, not the source
52
+ * the "Copy page" .md route serves - see context-menu.mdx).
53
+ *
54
+ * Respects an explicit import: if a file already imports its own `Callout`
55
+ * (a custom one, or re-exporting the built-in on purpose), that name is
56
+ * left alone rather than double-imported or overridden.
57
+ */
58
+ export function remarkInjectBuiltinComponents() {
59
+ return (tree) => {
60
+ const locallyBound = new Set();
61
+ visit(tree, 'mdxjsEsm', (node) => {
62
+ const body = node.data?.estree?.body;
63
+ if (!body) return;
64
+ for (const stmt of body) {
65
+ if (stmt.type !== 'ImportDeclaration') continue;
66
+ for (const specifier of stmt.specifiers) {
67
+ if (specifier.local?.name) locallyBound.add(specifier.local.name);
68
+ }
69
+ }
70
+ });
71
+
72
+ const needed = new Set();
73
+ visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node) => {
74
+ if (!node.name) return;
75
+ if (BUILTIN_COMPONENT_NAMES.includes(node.name) && !locallyBound.has(node.name)) {
76
+ needed.add(node.name);
77
+ }
78
+ });
79
+
80
+ if (needed.size === 0) return;
81
+
82
+ const names = [...needed].sort();
83
+ const source = `import { ${names.join(', ')} } from '${PACKAGE_SPECIFIER}';\n`;
84
+ const estree = acornParse(source, { ecmaVersion: 'latest', sourceType: 'module' });
85
+ tree.children.unshift({ type: 'mdxjsEsm', value: source, data: { estree } });
86
+ };
87
+ }
@@ -0,0 +1,66 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ // Matches `[[key]]` - alphanumerics, dots, dashes, underscores in the key,
4
+ // with optional whitespace padding (`[[ key ]]` also matches). Kept
5
+ // deliberately simple/text-only: this is prose substitution, not an
6
+ // expression language, so there's no support for defaults, nesting, or
7
+ // anything beyond "look this key up in writedocs.json's `variables` map".
8
+ //
9
+ // Double square brackets, not double curly braces (`{{key}}`, the more
10
+ // familiar templating convention from Handlebars/Mintlify-alikes) -
11
+ // deliberately. MDX reserves single curly braces for embedded JS
12
+ // expressions (`{jsExpression}`), and micromark-extension-mdx-expression
13
+ // tokenizes `{...}` at *parse* time, before any remark plugin (this one
14
+ // included) ever sees the file as an AST. `{{productName}}` in an .mdx
15
+ // file parses as one MDX expression container whose content is the JS
16
+ // object literal `{productName}` (shorthand for `{ productName:
17
+ // productName }`), not as literal text at all - by the time this plugin
18
+ // runs, there's no `text` node containing `{{productName}}` left to find,
19
+ // and the page throws `ReferenceError: productName is not defined` at
20
+ // render time instead. Confirmed empirically while building this
21
+ // feature's own fixture (`25-site-config/`) - not a hypothetical concern.
22
+ // Square brackets carry no such special meaning in MDX/CommonMark outside
23
+ // of the `[text](url)`/`[text][ref]` link forms, both of which require a
24
+ // `(` or second `[...]` immediately after - `[[key]]` alone never matches
25
+ // either, so it round-trips as plain text the same way `{{key}}` would
26
+ // have in an ordinary (non-MDX) Markdown file.
27
+ const PLACEHOLDER = /\[\[\s*([\w.-]+)\s*\]\]/g;
28
+
29
+ /**
30
+ * A remark plugin that replaces `[[key]]` placeholders in a page's prose
31
+ * with the corresponding value from writedocs.json's `variables` map (see
32
+ * variables: z.record(...) on docsConfigSchema in src/lib/config.ts) -
33
+ * e.g. `[[productName]]` becomes `Acme` everywhere a site sets
34
+ * `variables: { productName: "Acme" }`.
35
+ *
36
+ * Only visits `text`-type AST nodes, deliberately - `code`/`inlineCode`
37
+ * nodes (fenced/inline code) are a structurally separate node type in the
38
+ * MDX AST, so they're never visited here and a `[[...]]` written inside a
39
+ * code fence is left completely untouched - the same "don't mangle code"
40
+ * property remarkAutoHydrateSnippets and remarkInjectBuiltinComponents get
41
+ * from visiting narrow node-type sets rather than working on raw source
42
+ * text.
43
+ *
44
+ * A placeholder with no matching key is left as literal text (`[[typo]]`
45
+ * stays `[[typo]]`) rather than silently becoming an empty string - an
46
+ * unresolved placeholder rendering visibly in the built page is a much
47
+ * easier bug to spot than prose that's quietly missing a word.
48
+ *
49
+ * Takes `variables` as an options argument, so this attacher has to be
50
+ * passed to unified/remark as a `[remarkSubstituteVariables, variables]`
51
+ * tuple, not pre-called - see the comment on its own usage in
52
+ * astro.config.mjs for why calling it directly and handing unified the
53
+ * resulting transformer breaks the build.
54
+ */
55
+ export function remarkSubstituteVariables(variables) {
56
+ return (tree) => {
57
+ if (!variables || Object.keys(variables).length === 0) return;
58
+
59
+ visit(tree, 'text', (node) => {
60
+ if (!node.value.includes('[[')) return;
61
+ node.value = node.value.replace(PLACEHOLDER, (match, key) =>
62
+ Object.prototype.hasOwnProperty.call(variables, key) ? variables[key] : match
63
+ );
64
+ });
65
+ };
66
+ }
@@ -0,0 +1,84 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ // Every JSX tag name that can carry a `title` prop and render a linkable
4
+ // heading of its own - the 6 Callout variants remarkInjectBuiltinComponents
5
+ // also auto-imports (see its own BUILTIN_COMPONENT_NAMES), plus Accordion.
6
+ // Kept as its own literal list rather than importing that one, since this
7
+ // only cares about the subset that actually has a `title`/anchor at all
8
+ // (Card, Tabs, AccordionGroup, etc. don't). AccordionGroup is deliberately
9
+ // excluded - it has no `title` of its own, just wraps Accordion children
10
+ // that each get their own entry here independently.
11
+ const TITLED_COMPONENT_NAMES = ['Callout', 'Note', 'Info', 'Tip', 'Warning', 'Danger', 'Accordion'];
12
+
13
+ // Deliberately the same slugify Callout.astro/Accordion.astro themselves
14
+ // fall back to (see their own comments) - kept as a literal duplicate
15
+ // rather than a shared import, since this file runs inside
16
+ // astro.config.mjs's markdown pipeline, a different module graph than
17
+ // either component's own (same reasoning mdx-inject-builtins.js gives for
18
+ // not importing src/components/index.ts).
19
+ function slugify(value) {
20
+ return value
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9]+/g, '-')
23
+ .replace(/^-+|-+$/g, '');
24
+ }
25
+
26
+ /**
27
+ * A remark plugin that assigns a deduped `_titleId` attribute to every
28
+ * <Callout title="...">/<Note title="...">/<Accordion title="...">/etc.
29
+ * element on a page, mirroring the -2/-3 suffixing behavior Astro's own
30
+ * heading-id step (github-slugger, upstream in the pipeline) already gives
31
+ * duplicate headings - so two of these elements with the same title text
32
+ * get two distinct, independently-linkable ids instead of silently
33
+ * colliding on the same #slug (confirmed as a real bug: the Components
34
+ * page has two callouts both titled "Two ways to write this").
35
+ *
36
+ * One shared `seen` map across every tag name in TITLED_COMPONENT_NAMES,
37
+ * not one per tag - a Callout titled "Configuration" and an Accordion
38
+ * titled "Configuration" later on the same page are still two different
39
+ * elements racing for the same #configuration slug, so they need to be
40
+ * deduped against each other too, not just against their own tag name.
41
+ *
42
+ * Has to happen here, not inside Callout.astro/Accordion.astro
43
+ * themselves - neither one ever sees more than *one* of its own
44
+ * invocations at a time; it has no visibility into whether some earlier
45
+ * element on the same page already produced the same slug. Walking the
46
+ * whole page's MDX AST in one pass, in document order, is the only
47
+ * vantage point that can dedupe correctly - the exact same reason
48
+ * github-slugger itself runs once per document rather than being
49
+ * reimplemented per-heading.
50
+ *
51
+ * Only handles a literal string `title="..."` - mdast-util-mdx-jsx gives a
52
+ * plain JS string for that form's attribute value, vs. an
53
+ * mdxJsxAttributeValueExpression node for a dynamic `title={expr}`. Every
54
+ * real usage in this codebase passes a plain string; a dynamic one is left
55
+ * alone and falls back to that component's own un-deduped slugify(title) -
56
+ * same behavior as before this plugin existed.
57
+ *
58
+ * Scoped per MDX *file*, not per rendered page - a snippet imported into a
59
+ * page (see docs/dev/docs/snippets.mdx) is its own independently-compiled
60
+ * MDX module with its own AST, so a title duplicated between a page and a
61
+ * snippet it imports won't be caught here. Acceptable for the same reason
62
+ * remarkInjectBuiltinComponents' own per-file scope is: there's no single
63
+ * shared AST spanning both by the time either one's transform runs.
64
+ */
65
+ export function remarkTitleAnchorIds() {
66
+ return (tree) => {
67
+ const seen = new Map();
68
+ visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node) => {
69
+ if (!node.name || !TITLED_COMPONENT_NAMES.includes(node.name)) return;
70
+ const titleAttr = node.attributes?.find(
71
+ (attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'title'
72
+ );
73
+ if (!titleAttr || typeof titleAttr.value !== 'string') return;
74
+
75
+ const base = slugify(titleAttr.value);
76
+ if (!base) return;
77
+ const priorCount = seen.get(base) ?? 0;
78
+ seen.set(base, priorCount + 1);
79
+ const id = priorCount === 0 ? base : `${base}-${priorCount + 1}`;
80
+
81
+ node.attributes.push({ type: 'mdxJsxAttribute', name: '_titleId', value: id });
82
+ });
83
+ };
84
+ }
@@ -0,0 +1,72 @@
1
+ // Turns a ```mermaid fenced block into a plain <div class="mermaid">
2
+ // containing its raw (un-highlighted) source text, ready for mermaid.js
3
+ // to render into an SVG diagram client-side - see initMermaid() in
4
+ // [...slug].astro for the render/re-render-on-theme-change side of this.
5
+ //
6
+ // Registered in astro.config.mjs's markdown.rehypePlugins, which Astro
7
+ // runs *after* its own internal Shiki syntax-highlighting step (see
8
+ // @astrojs/markdown-remark's index.js) - astro.config.mjs also sets
9
+ // markdown.syntaxHighlight.excludeLangs: ['mermaid'], so a ```mermaid
10
+ // block reaches this plugin completely untouched by Shiki: a plain
11
+ // <pre><code class="language-mermaid">raw source</code></pre>, no
12
+ // tokenization, no theme-specific inline styles to strip back out. This
13
+ // plugin's only job is reshaping that into the flat container mermaid.js
14
+ // expects (a single element directly containing the diagram source, not
15
+ // nested in a <code>).
16
+ //
17
+ // Deliberately a hand-written recursive walk rather than pulling in
18
+ // unist-util-visit for one simple find-and-replace - mirrors
19
+ // shiki-code-block.js's own direct hast.children manipulation instead of
20
+ // reaching for a visitor library for something this small.
21
+
22
+ const LANGUAGE_MERMAID_RE = /\blanguage-mermaid\b/;
23
+
24
+ function hasClass(properties, re) {
25
+ const className = properties?.className;
26
+ if (Array.isArray(className)) return className.some((c) => typeof c === 'string' && re.test(c));
27
+ if (typeof className === 'string') return re.test(className);
28
+ return false;
29
+ }
30
+
31
+ /** A Shiki-excluded ```mermaid block always survives as exactly
32
+ * `<pre><code class="language-mermaid">...</code></pre>` (see
33
+ * highlightCodeBlocks() in @astrojs/markdown-remark - it only ever
34
+ * replaces the matched <code>'s parent when there's exactly one child),
35
+ * so both checks together are enough to identify one unambiguously. */
36
+ function isMermaidCodeBlock(node) {
37
+ if (node.type !== 'element' || node.tagName !== 'pre' || node.children.length !== 1) return false;
38
+ const code = node.children[0];
39
+ return code.type === 'element' && code.tagName === 'code' && hasClass(code.properties, LANGUAGE_MERMAID_RE);
40
+ }
41
+
42
+ function textOf(node) {
43
+ if (node.type === 'text') return node.value;
44
+ if (!node.children) return '';
45
+ return node.children.map(textOf).join('');
46
+ }
47
+
48
+ function walk(node) {
49
+ if (!node || !Array.isArray(node.children)) return;
50
+ node.children = node.children.map((child) => {
51
+ if (isMermaidCodeBlock(child)) {
52
+ const codeNode = child.children[0];
53
+ return {
54
+ type: 'element',
55
+ tagName: 'div',
56
+ // data-pagefind-ignore: raw mermaid syntax ("graph TD; A-->B;")
57
+ // makes for garbled, useless search excerpts - see the same
58
+ // attribute on [...slug].astro's .wd-prevnext nav.
59
+ properties: { className: ['mermaid'], 'data-pagefind-ignore': true },
60
+ children: [{ type: 'text', value: textOf(codeNode) }],
61
+ };
62
+ }
63
+ walk(child);
64
+ return child;
65
+ });
66
+ }
67
+
68
+ export function rehypeMermaid() {
69
+ return (tree) => {
70
+ walk(tree);
71
+ };
72
+ }