@classic-homes/theme-docs 0.1.0 → 0.2.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 (52) hide show
  1. package/dist/lib/components/Breadcrumbs.svelte +55 -0
  2. package/dist/lib/components/Breadcrumbs.svelte.d.ts +14 -0
  3. package/dist/lib/components/CategoryIndex.svelte +51 -0
  4. package/dist/lib/components/CategoryIndex.svelte.d.ts +13 -0
  5. package/dist/lib/components/DocPage.svelte +172 -0
  6. package/dist/lib/components/DocPage.svelte.d.ts +60 -0
  7. package/dist/lib/components/DocPager.svelte +49 -0
  8. package/dist/lib/components/DocPager.svelte.d.ts +12 -0
  9. package/dist/lib/components/MarkdownPage.svelte +3 -1
  10. package/dist/lib/components/MermaidDiagram.svelte +2 -0
  11. package/dist/lib/components/MermaidInit.svelte +2 -0
  12. package/dist/lib/components/TableOfContents.svelte +114 -125
  13. package/dist/lib/components/TableOfContents.svelte.d.ts +11 -4
  14. package/dist/lib/components/TagIndex.svelte +42 -0
  15. package/dist/lib/components/TagIndex.svelte.d.ts +14 -0
  16. package/dist/lib/components/TagList.svelte +45 -0
  17. package/dist/lib/components/TagList.svelte.d.ts +15 -0
  18. package/dist/lib/components/TocPanel.svelte +27 -9
  19. package/dist/lib/components/TocPanel.svelte.d.ts +10 -3
  20. package/dist/lib/components/enhance.d.ts +29 -0
  21. package/dist/lib/components/enhance.js +179 -0
  22. package/dist/lib/components/sidebar.d.ts +33 -0
  23. package/dist/lib/components/sidebar.js +84 -0
  24. package/dist/lib/content/browser.d.ts +6 -0
  25. package/dist/lib/content/browser.js +5 -0
  26. package/dist/lib/content/index.d.ts +13 -0
  27. package/dist/lib/content/index.js +12 -0
  28. package/dist/lib/content/load.d.ts +77 -0
  29. package/dist/lib/content/load.js +366 -0
  30. package/dist/lib/content/nav.d.ts +36 -0
  31. package/dist/lib/content/nav.js +81 -0
  32. package/dist/lib/content/render.d.ts +38 -0
  33. package/dist/lib/content/render.js +84 -0
  34. package/dist/lib/content/types.d.ts +90 -0
  35. package/dist/lib/content/types.js +5 -0
  36. package/dist/lib/index.d.ts +14 -2
  37. package/dist/lib/index.js +14 -2
  38. package/dist/lib/parser/api.d.ts +12 -0
  39. package/dist/lib/parser/api.js +10 -0
  40. package/dist/lib/parser/extensions.d.ts +27 -15
  41. package/dist/lib/parser/extensions.js +58 -53
  42. package/dist/lib/parser/index.d.ts +4 -1
  43. package/dist/lib/parser/index.js +104 -27
  44. package/dist/lib/sanitize/index.d.ts +11 -0
  45. package/dist/lib/sanitize/index.js +122 -0
  46. package/dist/lib/search/index.d.ts +57 -0
  47. package/dist/lib/search/index.js +82 -0
  48. package/dist/lib/styles/markdown.css +138 -0
  49. package/dist/lib/types/frontmatter.d.ts +21 -0
  50. package/dist/lib/vite/index.d.ts +17 -0
  51. package/dist/lib/vite/index.js +38 -0
  52. package/package.json +51 -4
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Shapes of the docs content model built by `loadDocs` and `renderDocs`.
3
+ * Plain data: safe to serialize into a bundle and to import from client code.
4
+ */
5
+ import type { FrontmatterData } from '../types/frontmatter.js';
6
+ import type { TocEntry } from '../types/docs.js';
7
+ /** One sidebar entry. A category's `route` is its linked doc or generated-index page. */
8
+ export type NavItem = {
9
+ type: 'page';
10
+ title: string;
11
+ route: string;
12
+ } | {
13
+ type: 'category';
14
+ label: string;
15
+ route?: string;
16
+ /** From `_category_.json`. Undefined means the site's default. */
17
+ collapsible?: boolean;
18
+ collapsed?: boolean;
19
+ items: NavItem[];
20
+ };
21
+ /** A markdown file, with its links rewritten to site routes. */
22
+ export interface DocSource {
23
+ /** Site route, e.g. `/docs/it/network/vpn` (`index.md` maps to its folder) */
24
+ route: string;
25
+ /** Path under the docs directory, posix separators, e.g. `it/network/vpn/index.md` */
26
+ file: string;
27
+ /** ID of the sidebar the page belongs to, or null when it sits outside every sidebar root */
28
+ sidebar: string | null;
29
+ /** `title` front matter, else the first `# heading`, else the file name */
30
+ title: string;
31
+ /** `sidebar_label` front matter: the page's name in navigation (Docusaurus parity) */
32
+ sidebarLabel?: string;
33
+ description: string;
34
+ tags: string[];
35
+ /** `sidebar_position` front matter */
36
+ position?: number;
37
+ /** The page's full front matter, including fields this package doesn't know */
38
+ frontmatter: FrontmatterData;
39
+ /** Markdown body (no front matter), links resolved */
40
+ markdown: string;
41
+ }
42
+ /** A sidebar autogenerated from one directory, as Docusaurus `{type: 'autogenerated', dirName}` does. */
43
+ export interface Sidebar {
44
+ /** The directory it was generated from (relative to the docs dir), or `default` for the root */
45
+ id: string;
46
+ /** The directory, relative to the docs dir ('' for the root) */
47
+ dir: string;
48
+ /** `_category_.json` label of the directory, else its name */
49
+ label: string;
50
+ /** `_category_.json` position of the directory */
51
+ position?: number;
52
+ /** Route of the directory itself */
53
+ route: string;
54
+ /** The directory's index page if it has one, else its shallowest, lowest-positioned page */
55
+ landingRoute: string;
56
+ items: NavItem[];
57
+ }
58
+ /** A generated-index page (`_category_.json` `link: { type: 'generated-index' }`). */
59
+ export interface CategoryPage {
60
+ route: string;
61
+ sidebar: string;
62
+ title: string;
63
+ description: string;
64
+ /** The category's items, in sidebar order */
65
+ items: NavItem[];
66
+ }
67
+ export interface Tag {
68
+ id: string;
69
+ label: string;
70
+ description: string;
71
+ }
72
+ export interface DocsContent {
73
+ pages: DocSource[];
74
+ sidebars: Sidebar[];
75
+ categories: CategoryPage[];
76
+ /** The tag vocabulary when one was given, else every tag used, in first-use order */
77
+ tags: Tag[];
78
+ /** Non-fatal problems, e.g. broken links when `onBrokenLinks` is `'warn'` */
79
+ warnings: string[];
80
+ }
81
+ /** A page after `renderDocs`: HTML instead of markdown, plus what layouts and search need. */
82
+ export interface RenderedDoc extends Omit<DocSource, 'markdown'> {
83
+ html: string;
84
+ /** Headings with IDs, all levels */
85
+ toc: TocEntry[];
86
+ /** Whether the body has its own `<h1>`. When it doesn't, the layout shows `title`. */
87
+ hasH1: boolean;
88
+ /** Searchable plain text: the page without markup, code blocks or diagrams */
89
+ text: string;
90
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shapes of the docs content model built by `loadDocs` and `renderDocs`.
3
+ * Plain data: safe to serialize into a bundle and to import from client code.
4
+ */
5
+ export {};
@@ -5,11 +5,15 @@
5
5
  * with syntax highlighting, frontmatter support, and rich markdown extensions.
6
6
  *
7
7
  * Supported Markdown Extensions:
8
- * - Admonitions/Callouts: :::note, :::tip, :::warning, :::important, :::caution
8
+ * - Admonitions/Callouts: :::note, :::info, :::tip, :::warning, :::important, :::caution, :::danger
9
9
  * - Footnotes: [^1] references and [^1]: definitions
10
10
  * - Definition Lists: Term followed by : Definition
11
11
  * - Mermaid Diagrams: ```mermaid code blocks (rendered client-side)
12
12
  *
13
+ * Node/server code (build scripts, loaders) should import the pipeline from
14
+ * `@classic-homes/theme-docs/parser`, which has no Svelte imports, and sanitize
15
+ * rendered HTML with `@classic-homes/theme-docs/sanitize`.
16
+ *
13
17
  * @packageDocumentation
14
18
  */
15
19
  export { default as MarkdownPage } from './components/MarkdownPage.svelte';
@@ -20,12 +24,20 @@ export { default as TableOfContents } from './components/TableOfContents.svelte'
20
24
  export { default as TocPanel } from './components/TocPanel.svelte';
21
25
  export { default as MermaidDiagram } from './components/MermaidDiagram.svelte';
22
26
  export { default as MermaidInit } from './components/MermaidInit.svelte';
27
+ export { default as DocPage } from './components/DocPage.svelte';
28
+ export { default as CategoryIndex } from './components/CategoryIndex.svelte';
29
+ export { default as Breadcrumbs } from './components/Breadcrumbs.svelte';
30
+ export { default as DocPager } from './components/DocPager.svelte';
31
+ export { default as TagList } from './components/TagList.svelte';
32
+ export { default as TagIndex } from './components/TagIndex.svelte';
23
33
  export { mountComponents } from './components/mount.js';
34
+ export { enhanceCodeBlocks, highlightTerms, labelDiagram } from './components/enhance.js';
35
+ export { toSidebarNav, revealActive, type ToSidebarNavOptions } from './components/sidebar.js';
24
36
  export type { AuthorInfo, FrontmatterData, ParsedMarkdown, ParseOptions, MarkdownPageProps, } from './types/frontmatter.js';
25
37
  export type { DocsItem, DocsSection, DocsHubConfig, DocsHubProps, DocsCardProps, TocEntry, TableOfContentsProps, } from './types/docs.js';
26
38
  export { parseMarkdown, extractToc } from './parser/index.js';
27
39
  export { createSlugger, type HeadingIdStyle } from './parser/slug.js';
28
40
  export { highlightCode, getHighlighter, escapeHtml } from './highlighter/index.js';
29
41
  export { cn } from './utils.js';
30
- export { ADMONITION_TYPES, type AdmonitionType, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, getAllExtensions, resetFootnoteStore, renderFootnotes, } from './parser/extensions.js';
42
+ export { ADMONITION_TYPES, type AdmonitionType, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, componentExtension, getAllExtensions, createFootnoteStore, type FootnoteStore, resetFootnoteStore, renderFootnotes, renderMermaidPlaceholder, } from './parser/extensions.js';
31
43
  export { tv, type VariantProps } from 'tailwind-variants';
package/dist/lib/index.js CHANGED
@@ -5,11 +5,15 @@
5
5
  * with syntax highlighting, frontmatter support, and rich markdown extensions.
6
6
  *
7
7
  * Supported Markdown Extensions:
8
- * - Admonitions/Callouts: :::note, :::tip, :::warning, :::important, :::caution
8
+ * - Admonitions/Callouts: :::note, :::info, :::tip, :::warning, :::important, :::caution, :::danger
9
9
  * - Footnotes: [^1] references and [^1]: definitions
10
10
  * - Definition Lists: Term followed by : Definition
11
11
  * - Mermaid Diagrams: ```mermaid code blocks (rendered client-side)
12
12
  *
13
+ * Node/server code (build scripts, loaders) should import the pipeline from
14
+ * `@classic-homes/theme-docs/parser`, which has no Svelte imports, and sanitize
15
+ * rendered HTML with `@classic-homes/theme-docs/sanitize`.
16
+ *
13
17
  * @packageDocumentation
14
18
  */
15
19
  // Components
@@ -21,13 +25,21 @@ export { default as TableOfContents } from './components/TableOfContents.svelte'
21
25
  export { default as TocPanel } from './components/TocPanel.svelte';
22
26
  export { default as MermaidDiagram } from './components/MermaidDiagram.svelte';
23
27
  export { default as MermaidInit } from './components/MermaidInit.svelte';
28
+ export { default as DocPage } from './components/DocPage.svelte';
29
+ export { default as CategoryIndex } from './components/CategoryIndex.svelte';
30
+ export { default as Breadcrumbs } from './components/Breadcrumbs.svelte';
31
+ export { default as DocPager } from './components/DocPager.svelte';
32
+ export { default as TagList } from './components/TagList.svelte';
33
+ export { default as TagIndex } from './components/TagIndex.svelte';
24
34
  export { mountComponents } from './components/mount.js';
35
+ export { enhanceCodeBlocks, highlightTerms, labelDiagram } from './components/enhance.js';
36
+ export { toSidebarNav, revealActive } from './components/sidebar.js';
25
37
  // Utilities
26
38
  export { parseMarkdown, extractToc } from './parser/index.js';
27
39
  export { createSlugger } from './parser/slug.js';
28
40
  export { highlightCode, getHighlighter, escapeHtml } from './highlighter/index.js';
29
41
  export { cn } from './utils.js';
30
42
  // Markdown Extensions
31
- export { ADMONITION_TYPES, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, getAllExtensions, resetFootnoteStore, renderFootnotes, } from './parser/extensions.js';
43
+ export { ADMONITION_TYPES, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, componentExtension, getAllExtensions, createFootnoteStore, resetFootnoteStore, renderFootnotes, renderMermaidPlaceholder, } from './parser/extensions.js';
32
44
  // Re-export tailwind-variants types for consumer convenience
33
45
  export { tv } from 'tailwind-variants';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@classic-homes/theme-docs/parser` — the markdown pipeline without any Svelte imports.
3
+ *
4
+ * Safe to load in Node (build scripts, server loaders, workers) and in non-Svelte bundles
5
+ * such as a CMS preview. The package root re-exports all of this alongside the components.
6
+ */
7
+ export { parseMarkdown, extractToc } from './index.js';
8
+ export { createSlugger, type HeadingIdStyle } from './slug.js';
9
+ export { ADMONITION_TYPES, type AdmonitionType, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, componentExtension, getAllExtensions, createFootnoteStore, type FootnoteStore, resetFootnoteStore, renderFootnotes, renderMermaidPlaceholder, } from './extensions.js';
10
+ export { highlightCode, getHighlighter, escapeHtml } from '../highlighter/index.js';
11
+ export type { AuthorInfo, FrontmatterData, ParsedMarkdown, ParseOptions, } from '../types/frontmatter.js';
12
+ export type { TocEntry } from '../types/docs.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@classic-homes/theme-docs/parser` — the markdown pipeline without any Svelte imports.
3
+ *
4
+ * Safe to load in Node (build scripts, server loaders, workers) and in non-Svelte bundles
5
+ * such as a CMS preview. The package root re-exports all of this alongside the components.
6
+ */
7
+ export { parseMarkdown, extractToc } from './index.js';
8
+ export { createSlugger } from './slug.js';
9
+ export { ADMONITION_TYPES, admonitionExtension, footnoteExtension, definitionListExtension, mermaidExtension, componentExtension, getAllExtensions, createFootnoteStore, resetFootnoteStore, renderFootnotes, renderMermaidPlaceholder, } from './extensions.js';
10
+ export { highlightCode, getHighlighter, escapeHtml } from '../highlighter/index.js';
@@ -8,7 +8,7 @@
8
8
  * - Mermaid diagram placeholders (rendered client-side)
9
9
  * - Component placeholders for allowlisted `<Name prop="…" />` tags (mounted client-side)
10
10
  */
11
- import type { MarkedExtension } from 'marked';
11
+ import type { MarkedExtension, Tokens } from 'marked';
12
12
  /**
13
13
  * Admonition types and their corresponding icons/classes
14
14
  */
@@ -34,7 +34,24 @@ export type AdmonitionType = (typeof ADMONITION_TYPES)[number];
34
34
  */
35
35
  export declare function admonitionExtension(): MarkedExtension;
36
36
  /**
37
- * Reset footnote store (call before parsing a new document)
37
+ * Footnotes collected while parsing one document.
38
+ *
39
+ * Each `parseMarkdown` call creates its own store, so concurrent parses (e.g. a server
40
+ * rendering many pages at once) never mix up each other's footnotes.
41
+ */
42
+ export interface FootnoteStore {
43
+ definitions: Map<string, {
44
+ content: string;
45
+ tokens: Tokens.Generic[];
46
+ }>;
47
+ references: Set<string>;
48
+ }
49
+ export declare function createFootnoteStore(): FootnoteStore;
50
+ /**
51
+ * Reset the default footnote store.
52
+ *
53
+ * @deprecated `parseMarkdown` now keeps a store per call. Only needed if you build a
54
+ * `Marked` instance yourself from `footnoteExtension()` without passing a store.
38
55
  */
39
56
  export declare function resetFootnoteStore(): void;
40
57
  /**
@@ -45,23 +62,16 @@ export declare function resetFootnoteStore(): void;
45
62
  *
46
63
  * [^1]: This is the footnote content.
47
64
  */
48
- export declare function footnoteExtension(): MarkedExtension;
65
+ export declare function footnoteExtension(store?: FootnoteStore): MarkedExtension;
49
66
  /**
50
67
  * Render collected footnotes as a section
51
68
  * Call this after parsing to get the footnotes HTML
52
- */
53
- export declare function renderFootnotes(): string;
54
- /**
55
- * Definition list extension for marked
56
- *
57
- * Syntax:
58
- * Term 1
59
- * : Definition for term 1
60
69
  *
61
- * Term 2
62
- * : Definition for term 2
63
- * : Another definition for term 2
70
+ * @param store - The store the footnote extension wrote to (default: the shared store)
71
+ * @param renderInline - Renders a footnote's inline tokens to HTML. Without it the
72
+ * footnote text is escaped and shown as written.
64
73
  */
74
+ export declare function renderFootnotes(store?: FootnoteStore, renderInline?: (tokens: Tokens.Generic[]) => string): string;
65
75
  export declare function definitionListExtension(): MarkedExtension;
66
76
  /**
67
77
  * Mermaid code block extension for marked
@@ -75,6 +85,7 @@ export declare function definitionListExtension(): MarkedExtension;
75
85
  * A --> B
76
86
  * ```
77
87
  */
88
+ export declare function renderMermaidPlaceholder(code: string): string;
78
89
  export declare function mermaidExtension(): MarkedExtension;
79
90
  /**
80
91
  * Component placeholder extension for marked
@@ -93,5 +104,6 @@ export declare function componentExtension(names: readonly string[]): MarkedExte
93
104
  * Get all markdown extensions
94
105
  *
95
106
  * @param components - Names of components to render as placeholders (see `componentExtension`)
107
+ * @param footnotes - Store for this document's footnotes (default: the shared store)
96
108
  */
97
- export declare function getAllExtensions(components?: readonly string[]): MarkedExtension[];
109
+ export declare function getAllExtensions(components?: readonly string[], footnotes?: FootnoteStore): MarkedExtension[];
@@ -76,13 +76,13 @@ export function admonitionExtension() {
76
76
  const innerHtml = this.parser.parse(token.tokens ?? []);
77
77
  const titleHtml = this.parser.parseInline(token.titleTokens ?? []);
78
78
  const iconMap = {
79
- note: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
80
- info: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`,
81
- tip: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>`,
82
- warning: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
83
- important: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>`,
84
- caution: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7.86 2h8.28L22 7.86v8.28L16.14 22H7.86L2 16.14V7.86L7.86 2z"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
85
- danger: `<svg class="admonition-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>`,
79
+ note: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
80
+ info: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`,
81
+ tip: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>`,
82
+ warning: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
83
+ important: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>`,
84
+ caution: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7.86 2h8.28L22 7.86v8.28L16.14 22H7.86L2 16.14V7.86L7.86 2z"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
85
+ danger: `<svg class="admonition-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>`,
86
86
  };
87
87
  return `<div class="admonition admonition-${token.admonitionType}">
88
88
  <div class="admonition-heading">
@@ -96,18 +96,19 @@ export function admonitionExtension() {
96
96
  ],
97
97
  };
98
98
  }
99
- let footnoteStore = {
100
- definitions: new Map(),
101
- references: new Set(),
102
- };
99
+ export function createFootnoteStore() {
100
+ return { definitions: new Map(), references: new Set() };
101
+ }
102
+ /** Store used when an extension is created without one (the pre-0.2 global API). */
103
+ let defaultFootnoteStore = createFootnoteStore();
103
104
  /**
104
- * Reset footnote store (call before parsing a new document)
105
+ * Reset the default footnote store.
106
+ *
107
+ * @deprecated `parseMarkdown` now keeps a store per call. Only needed if you build a
108
+ * `Marked` instance yourself from `footnoteExtension()` without passing a store.
105
109
  */
106
110
  export function resetFootnoteStore() {
107
- footnoteStore = {
108
- definitions: new Map(),
109
- references: new Set(),
110
- };
111
+ defaultFootnoteStore = createFootnoteStore();
111
112
  }
112
113
  /**
113
114
  * Footnotes extension for marked
@@ -117,7 +118,8 @@ export function resetFootnoteStore() {
117
118
  *
118
119
  * [^1]: This is the footnote content.
119
120
  */
120
- export function footnoteExtension() {
121
+ export function footnoteExtension(store) {
122
+ const footnotes = () => store ?? defaultFootnoteStore;
121
123
  return {
122
124
  extensions: [
123
125
  // Footnote definition: [^id]: content
@@ -141,7 +143,7 @@ export function footnoteExtension() {
141
143
  tokens: [],
142
144
  };
143
145
  this.lexer.inline(content, token.tokens);
144
- footnoteStore.definitions.set(id, { content, tokens: token.tokens });
146
+ footnotes().definitions.set(id, { content, tokens: token.tokens });
145
147
  return token;
146
148
  }
147
149
  return undefined;
@@ -162,7 +164,7 @@ export function footnoteExtension() {
162
164
  const match = src.match(/^\[\^([^\]]+)\](?!:)/);
163
165
  if (match) {
164
166
  const id = match[1];
165
- footnoteStore.references.add(id);
167
+ footnotes().references.add(id);
166
168
  return {
167
169
  type: 'footnoteRef',
168
170
  raw: match[0],
@@ -172,7 +174,8 @@ export function footnoteExtension() {
172
174
  return undefined;
173
175
  },
174
176
  renderer(token) {
175
- return `<sup class="footnote-ref"><a href="#fn-${token.id}" id="fnref-${token.id}">[${token.id}]</a></sup>`;
177
+ const id = escapeHtml(token.id);
178
+ return `<sup class="footnote-ref"><a href="#fn-${id}" id="fnref-${id}" aria-label="Footnote ${id}">[${id}]</a></sup>`;
176
179
  },
177
180
  },
178
181
  ],
@@ -181,38 +184,33 @@ export function footnoteExtension() {
181
184
  /**
182
185
  * Render collected footnotes as a section
183
186
  * Call this after parsing to get the footnotes HTML
187
+ *
188
+ * @param store - The store the footnote extension wrote to (default: the shared store)
189
+ * @param renderInline - Renders a footnote's inline tokens to HTML. Without it the
190
+ * footnote text is escaped and shown as written.
184
191
  */
185
- export function renderFootnotes() {
186
- if (footnoteStore.definitions.size === 0) {
192
+ export function renderFootnotes(store = defaultFootnoteStore, renderInline) {
193
+ if (store.definitions.size === 0) {
187
194
  return '';
188
195
  }
189
196
  const items = [];
190
- for (const [id, { content }] of footnoteStore.definitions) {
191
- if (footnoteStore.references.has(id)) {
197
+ for (const [rawId, { content, tokens }] of store.definitions) {
198
+ if (store.references.has(rawId)) {
199
+ const id = escapeHtml(rawId);
200
+ const body = renderInline ? renderInline(tokens) : escapeHtml(content);
192
201
  items.push(`<li id="fn-${id}" class="footnote-item">
193
- <p>${content} <a href="#fnref-${id}" class="footnote-backref">↩</a></p>
202
+ <p>${body} <a href="#fnref-${id}" class="footnote-backref" aria-label="Back to reference ${id}">↩</a></p>
194
203
  </li>`);
195
204
  }
196
205
  }
197
206
  if (items.length === 0) {
198
207
  return '';
199
208
  }
200
- return `<section class="footnotes">
209
+ return `<section class="footnotes" aria-label="Footnotes">
201
210
  <hr class="footnotes-separator" />
202
211
  <ol class="footnotes-list">${items.join('')}</ol>
203
212
  </section>`;
204
213
  }
205
- /**
206
- * Definition list extension for marked
207
- *
208
- * Syntax:
209
- * Term 1
210
- * : Definition for term 1
211
- *
212
- * Term 2
213
- * : Definition for term 2
214
- * : Another definition for term 2
215
- */
216
214
  export function definitionListExtension() {
217
215
  return {
218
216
  extensions: [
@@ -254,6 +252,11 @@ export function definitionListExtension() {
254
252
  if (items.length === 0) {
255
253
  return undefined;
256
254
  }
255
+ // Terms and definitions are inline markdown, like everywhere else in a page
256
+ for (const item of items) {
257
+ item.termTokens = this.lexer.inlineTokens(item.term);
258
+ item.definitionTokens = item.definitions.map((def) => this.lexer.inlineTokens(def));
259
+ }
257
260
  return {
258
261
  type: 'defList',
259
262
  raw,
@@ -265,8 +268,10 @@ export function definitionListExtension() {
265
268
  renderer(token) {
266
269
  const itemsHtml = token.items
267
270
  .map((item) => {
268
- const defsHtml = item.definitions.map((def) => `<dd>${def}</dd>`).join('');
269
- return `<dt>${item.term}</dt>${defsHtml}`;
271
+ const defsHtml = item.definitionTokens
272
+ .map((tokens) => `<dd>${this.parser.parseInline(tokens)}</dd>`)
273
+ .join('');
274
+ return `<dt>${this.parser.parseInline(item.termTokens)}</dt>${defsHtml}`;
270
275
  })
271
276
  .join('');
272
277
  return `<dl class="definition-list">${itemsHtml}</dl>`;
@@ -287,6 +292,14 @@ export function definitionListExtension() {
287
292
  * A --> B
288
293
  * ```
289
294
  */
295
+ export function renderMermaidPlaceholder(code) {
296
+ // A placeholder div with the mermaid code; the client-side Mermaid library picks it up.
297
+ // The <pre> shows the source until (or if) mermaid renders it.
298
+ const escapedCode = escapeHtml(code);
299
+ return `<div class="mermaid-diagram" data-mermaid="${escapedCode}">
300
+ <pre class="mermaid">${escapedCode}</pre>
301
+ </div>`;
302
+ }
290
303
  export function mermaidExtension() {
291
304
  return {
292
305
  extensions: [
@@ -294,10 +307,10 @@ export function mermaidExtension() {
294
307
  name: 'mermaidBlock',
295
308
  level: 'block',
296
309
  start(src) {
297
- return src.match(/^```mermaid/)?.index;
310
+ return src.match(/^```mermaid[ \t]*\n/)?.index;
298
311
  },
299
312
  tokenizer(src) {
300
- const match = src.match(/^```mermaid\n([\s\S]*?)```/);
313
+ const match = src.match(/^```mermaid[ \t]*\n([\s\S]*?)```/);
301
314
  if (match) {
302
315
  return {
303
316
  type: 'mermaidBlock',
@@ -308,16 +321,7 @@ export function mermaidExtension() {
308
321
  return undefined;
309
322
  },
310
323
  renderer(token) {
311
- // Render as a placeholder div with the mermaid code
312
- // The client-side Mermaid library will pick this up
313
- const escapedCode = token.code
314
- .replace(/&/g, '&amp;')
315
- .replace(/</g, '&lt;')
316
- .replace(/>/g, '&gt;')
317
- .replace(/"/g, '&quot;');
318
- return `<div class="mermaid-diagram" data-mermaid="${escapedCode}">
319
- <pre class="mermaid">${token.code}</pre>
320
- </div>`;
324
+ return renderMermaidPlaceholder(token.code);
321
325
  },
322
326
  },
323
327
  ],
@@ -367,11 +371,12 @@ export function componentExtension(names) {
367
371
  * Get all markdown extensions
368
372
  *
369
373
  * @param components - Names of components to render as placeholders (see `componentExtension`)
374
+ * @param footnotes - Store for this document's footnotes (default: the shared store)
370
375
  */
371
- export function getAllExtensions(components = []) {
376
+ export function getAllExtensions(components = [], footnotes) {
372
377
  return [
373
378
  admonitionExtension(),
374
- footnoteExtension(),
379
+ footnoteExtension(footnotes),
375
380
  definitionListExtension(),
376
381
  mermaidExtension(),
377
382
  ...(components.length > 0 ? [componentExtension(components)] : []),
@@ -18,8 +18,11 @@ import type { ParsedMarkdown, ParseOptions } from '../types/index.js';
18
18
  * @param options.theme - Shiki theme for code highlighting ('github-dark' | 'github-light' | 'one-dark-pro')
19
19
  * @param options.generateHeadingIds - Whether to generate IDs for headings (default: true)
20
20
  * @param options.headingIdStyle - 'default', or 'github' for GitHub/Docusaurus-compatible IDs
21
+ * @param options.headingAnchors - Add a `#` permalink to each heading (default: false)
21
22
  * @param options.components - Tag names to render as component placeholders (see `mountComponents`)
22
- * @returns Parsed markdown with frontmatter data, cleaned markdown, and rendered HTML
23
+ * @param options.externalLinks - Open http(s) links in a new tab, announced to screen readers
24
+ * @param options.langAlias - Map fence names to Shiki languages, e.g. `{ ios: 'text' }`
25
+ * @returns Parsed markdown with frontmatter data, cleaned markdown, rendered HTML, and TOC
23
26
  *
24
27
  * @example
25
28
  * ```typescript