@fuzdev/mdz 0.2.0 → 0.3.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 (56) hide show
  1. package/dist/Mdz.svelte +15 -7
  2. package/dist/Mdz.svelte.d.ts +9 -2
  3. package/dist/Mdz.svelte.d.ts.map +1 -1
  4. package/dist/MdzNodeView.svelte +118 -112
  5. package/dist/MdzNodeView.svelte.d.ts +6 -2
  6. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  7. package/dist/MdzStream.svelte +3 -3
  8. package/dist/MdzStream.svelte.d.ts.map +1 -1
  9. package/dist/MdzStreamNodeView.svelte +117 -114
  10. package/dist/MdzStreamNodeView.svelte.d.ts +6 -2
  11. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
  12. package/dist/mdz.d.ts +6 -0
  13. package/dist/mdz.d.ts.map +1 -1
  14. package/dist/mdz.js +8 -0
  15. package/dist/mdz_debug_work.d.ts +38 -0
  16. package/dist/mdz_debug_work.d.ts.map +1 -0
  17. package/dist/mdz_debug_work.js +37 -0
  18. package/dist/mdz_helpers.d.ts +61 -0
  19. package/dist/mdz_helpers.d.ts.map +1 -1
  20. package/dist/mdz_helpers.js +72 -0
  21. package/dist/mdz_lexer.d.ts +12 -0
  22. package/dist/mdz_lexer.d.ts.map +1 -1
  23. package/dist/mdz_lexer.js +164 -24
  24. package/dist/mdz_stream_parser_inline.d.ts.map +1 -1
  25. package/dist/mdz_stream_parser_inline.js +19 -17
  26. package/dist/mdz_stream_parser_link.d.ts.map +1 -1
  27. package/dist/mdz_stream_parser_link.js +88 -56
  28. package/dist/mdz_stream_parser_list.d.ts.map +1 -1
  29. package/dist/mdz_stream_parser_list.js +10 -2
  30. package/dist/mdz_stream_parser_state.d.ts +29 -0
  31. package/dist/mdz_stream_parser_state.d.ts.map +1 -1
  32. package/dist/mdz_stream_parser_state.js +52 -3
  33. package/dist/mdz_stream_parser_table.d.ts +3 -2
  34. package/dist/mdz_stream_parser_table.d.ts.map +1 -1
  35. package/dist/mdz_stream_parser_table.js +18 -7
  36. package/dist/mdz_stream_parser_text.d.ts.map +1 -1
  37. package/dist/mdz_stream_parser_text.js +35 -24
  38. package/dist/mdz_to_svelte.d.ts.map +1 -1
  39. package/dist/mdz_to_svelte.js +28 -31
  40. package/dist/mdz_token_parser.d.ts.map +1 -1
  41. package/dist/mdz_token_parser.js +15 -16
  42. package/dist/svelte_preprocess_mdz.js +31 -37
  43. package/package.json +7 -4
  44. package/src/lib/mdz.ts +9 -0
  45. package/src/lib/mdz_debug_work.ts +38 -0
  46. package/src/lib/mdz_helpers.ts +97 -0
  47. package/src/lib/mdz_lexer.ts +168 -28
  48. package/src/lib/mdz_stream_parser_inline.ts +21 -17
  49. package/src/lib/mdz_stream_parser_link.ts +89 -56
  50. package/src/lib/mdz_stream_parser_list.ts +10 -4
  51. package/src/lib/mdz_stream_parser_state.ts +76 -4
  52. package/src/lib/mdz_stream_parser_table.ts +18 -7
  53. package/src/lib/mdz_stream_parser_text.ts +38 -37
  54. package/src/lib/mdz_to_svelte.ts +23 -31
  55. package/src/lib/mdz_token_parser.ts +17 -20
  56. package/src/lib/svelte_preprocess_mdz.ts +34 -35
package/dist/Mdz.svelte CHANGED
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type {SvelteHTMLElements} from 'svelte/elements';
3
+ import {DEV} from 'esm-env';
3
4
 
4
- import {mdz_parse} from './mdz.js';
5
+ import {mdz_parse, type MdzNode} from './mdz.js';
5
6
  import MdzNodeView from './MdzNodeView.svelte';
6
7
  import {
7
8
  mdz_base_context,
@@ -11,12 +12,12 @@
11
12
 
12
13
  const {
13
14
  content,
15
+ nodes: nodes_prop,
14
16
  inline = false,
15
17
  whitespace,
16
18
  base,
17
19
  ...rest
18
20
  }: (SvelteHTMLElements['div'] | SvelteHTMLElements['span']) & {
19
- content: string;
20
21
  inline?: boolean;
21
22
  /**
22
23
  * Sets `white-space` on the wrapper. When omitted, whitespace collapses
@@ -25,15 +26,22 @@
25
26
  */
26
27
  whitespace?: MdzWhitespace;
27
28
  base?: string;
28
- } = $props();
29
+ } & // pass `content` to parse at render, or `nodes` to render a pre-parsed
30
+ // tree (e.g. build-time-parsed docs content) and skip `mdz_parse` entirely
31
+ (| {content: string; nodes?: undefined}
32
+ | {content?: undefined; nodes: Array<MdzNode>}
33
+ ) = $props();
29
34
 
30
35
  mdz_set_context_with_fallback(mdz_base_context, () => base);
31
36
 
32
- const nodes = $derived(mdz_parse(content));
37
+ const nodes = $derived.by(() => {
38
+ if (DEV && (content === undefined) === (nodes_prop === undefined)) {
39
+ throw new Error('Mdz: pass exactly one of `content` or `nodes`');
40
+ }
41
+ return nodes_prop ?? mdz_parse(content ?? '');
42
+ });
33
43
  </script>
34
44
 
35
45
  <svelte:element this={inline ? 'span' : 'div'} {...rest} style:white-space={whitespace}>
36
- {#each nodes as node (node)}
37
- <MdzNodeView {node} />
38
- {/each}
46
+ <MdzNodeView {nodes} />
39
47
  </svelte:element>
@@ -1,7 +1,7 @@
1
1
  import type { SvelteHTMLElements } from 'svelte/elements';
2
+ import { type MdzNode } from './mdz.js';
2
3
  import { type MdzWhitespace } from './mdz_contexts.js';
3
4
  type $$ComponentProps = (SvelteHTMLElements['div'] | SvelteHTMLElements['span']) & {
4
- content: string;
5
5
  inline?: boolean;
6
6
  /**
7
7
  * Sets `white-space` on the wrapper. When omitted, whitespace collapses
@@ -10,7 +10,14 @@ type $$ComponentProps = (SvelteHTMLElements['div'] | SvelteHTMLElements['span'])
10
10
  */
11
11
  whitespace?: MdzWhitespace;
12
12
  base?: string;
13
- };
13
+ } & // pass `content` to parse at render, or `nodes` to render a pre-parsed
14
+ ({
15
+ content: string;
16
+ nodes?: undefined;
17
+ } | {
18
+ content?: undefined;
19
+ nodes: Array<MdzNode>;
20
+ });
14
21
  declare const Mdz: import("svelte").Component<$$ComponentProps, {}, "">;
15
22
  type Mdz = ReturnType<typeof Mdz>;
16
23
  export default Mdz;
@@ -1 +1 @@
1
- {"version":3,"file":"Mdz.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Mdz.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAIxD,OAAO,EAGL,KAAK,aAAa,EAClB,MAAM,mBAAmB,CAAC;AAE3B,KAAK,gBAAgB,GAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,GAAG;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AA6BH,QAAA,MAAM,GAAG,sDAAwC,CAAC;AAClD,KAAK,GAAG,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;AAClC,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"Mdz.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Mdz.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,UAAU,CAAC;AAEjD,OAAO,EAGL,KAAK,aAAa,EAClB,MAAM,mBAAmB,CAAC;AAE3B,KAAK,gBAAgB,GAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,GAAG;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,uEAAuE;AAE1E,CAAG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAC,GACpC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;CAAC,CAC9C,CAAC;AAkCJ,QAAA,MAAM,GAAG,sDAAwC,CAAC;AAClD,KAAK,GAAG,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;AAClC,eAAe,GAAG,CAAC"}
@@ -2,12 +2,7 @@
2
2
  import {resolve} from '$app/paths';
3
3
 
4
4
  import type {MdzNode, MdzNodeTableRow, MdzTableAlign} from './mdz.js';
5
- import {
6
- mdz_resolve_relative_path,
7
- mdz_is_safe_reference,
8
- mdz_is_void_element,
9
- } from './mdz_helpers.js';
10
- import MdzNodeView from './MdzNodeView.svelte';
5
+ import {mdz_classify_link, mdz_is_void_element} from './mdz_helpers.js';
11
6
  import {
12
7
  mdz_components_context,
13
8
  mdz_elements_context,
@@ -18,9 +13,10 @@
18
13
 
19
14
  const {
20
15
  node,
21
- }: {
22
- node: MdzNode;
23
- } = $props();
16
+ nodes,
17
+ }: // exactly one of `node`/`nodes` — the array form renders a whole tree
18
+ // through this single component instance (used by `Mdz`)
19
+ {node: MdzNode; nodes?: undefined} | {node?: undefined; nodes: Array<MdzNode>} = $props();
24
20
 
25
21
  const get_components = mdz_components_context.get_maybe();
26
22
  const get_elements = mdz_elements_context.get_maybe();
@@ -29,122 +25,132 @@
29
25
  const get_codeblock = mdz_codeblock_context.get_maybe();
30
26
  </script>
31
27
 
32
- {#if node.type === 'Element'}
33
- {@const element_config = get_elements?.()?.get(node.name)}
34
- {#if element_config !== undefined}
35
- {#if mdz_is_void_element(node.name)}
36
- <!-- void elements cannot have content — any parsed children are dropped -->
37
- <svelte:element this={node.name} />
28
+ <!-- The tree renders through recursive snippets rather than recursive
29
+ component instances — one component (and one set of context reads) per
30
+ tree instead of per node. -->
31
+ {#if nodes}
32
+ {@render render_children(nodes)}
33
+ {:else if node}
34
+ {@render render_node(node)}
35
+ {/if}
36
+
37
+ {#snippet render_node(node: MdzNode)}
38
+ {#if node.type === 'Element'}
39
+ {@const element_config = get_elements?.()?.get(node.name)}
40
+ {#if element_config !== undefined}
41
+ {#if mdz_is_void_element(node.name)}
42
+ <!-- void elements cannot have content — any parsed children are dropped -->
43
+ <svelte:element this={node.name} />
44
+ {:else}
45
+ <svelte:element this={node.name}>
46
+ {#if node.children.length > 0}
47
+ {@render render_children(node.children)}
48
+ {/if}
49
+ </svelte:element>
50
+ {/if}
38
51
  {:else}
39
- <svelte:element this={node.name}>
52
+ {@render render_unregistered_tag(node.name, node.children)}
53
+ {/if}
54
+ {:else if node.type === 'Component'}
55
+ {@const Component = get_components?.()?.get(node.name)}
56
+ {#if Component}
57
+ <Component>
40
58
  {#if node.children.length > 0}
41
59
  {@render render_children(node.children)}
42
60
  {/if}
43
- </svelte:element>
61
+ </Component>
62
+ {:else}
63
+ {@render render_unregistered_tag(node.name, node.children)}
44
64
  {/if}
45
- {:else}
46
- {@render render_unregistered_tag(node.name, node.children)}
47
- {/if}
48
- {:else if node.type === 'Component'}
49
- {@const Component = get_components?.()?.get(node.name)}
50
- {#if Component}
51
- <Component>
52
- {#if node.children.length > 0}
53
- {@render render_children(node.children)}
54
- {/if}
55
- </Component>
56
- {:else}
57
- {@render render_unregistered_tag(node.name, node.children)}
58
- {/if}
59
- {:else if node.type === 'Text'}
60
- {node.content}
61
- {:else if node.type === 'Code'}
62
- {@const Code = get_code?.()}
63
- {#if Code}
64
- <Code reference={node.content} />
65
- {:else}
66
- <code>{node.content}</code>
67
- {/if}
68
- {:else if node.type === 'Bold'}
69
- <strong>{@render render_children(node.children)}</strong>
70
- {:else if node.type === 'Italic'}
71
- <em>{@render render_children(node.children)}</em>
72
- {:else if node.type === 'Strikethrough'}
73
- <s>{@render render_children(node.children)}</s>
74
- {:else if node.type === 'Link'}
75
- {@const {reference} = node}
76
- {#if !mdz_is_safe_reference(reference)}
77
- {@render render_children(node.children)}
78
- {:else if node.link_type === 'internal'}
79
- {@const skip_resolve = reference.startsWith('#') || reference.startsWith('?')}
80
- {@const mdz_base = get_mdz_base?.()}
81
- {#if reference.startsWith('.') && mdz_base}
82
- {@const resolved = mdz_resolve_relative_path(reference, mdz_base)}
83
- <a href={resolve(resolved as any)}>{@render render_children(node.children)}</a>
84
- {:else if skip_resolve || reference.startsWith('.') || !reference.startsWith('/')}
85
- <!-- Fragment, query, and relative links (including bare references like `foo`) skip resolve() -->
86
- <!-- resolve() only accepts absolute pathnames or route IDs and throws on anything else -->
87
- <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
88
- <a href={reference}>{@render render_children(node.children)}</a>
65
+ {:else if node.type === 'Text'}
66
+ {node.content}
67
+ {:else if node.type === 'Code'}
68
+ {@const Code = get_code?.()}
69
+ {#if Code}
70
+ <Code reference={node.content} />
89
71
  {:else}
90
- <a href={resolve(reference as any)}>{@render render_children(node.children)}</a>
72
+ <code>{node.content}</code>
91
73
  {/if}
92
- {:else}
93
- <!-- external link -->
94
- <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
95
- <a href={reference} target="_blank" rel="noopener">{@render render_children(node.children)}</a>
96
- {/if}
97
- {:else if node.type === 'Paragraph'}
98
- <p>{@render render_children(node.children)}</p>
99
- {:else if node.type === 'List'}
100
- {#if node.ordered}
101
- <ol start={node.start_number === 1 ? undefined : node.start_number}>
74
+ {:else if node.type === 'Bold'}
75
+ <strong>{@render render_children(node.children)}</strong>
76
+ {:else if node.type === 'Italic'}
77
+ <em>{@render render_children(node.children)}</em>
78
+ {:else if node.type === 'Strikethrough'}
79
+ <s>{@render render_children(node.children)}</s>
80
+ {:else if node.type === 'Link'}
81
+ {@const link = mdz_classify_link(node.reference, node.link_type, get_mdz_base?.())}
82
+ {#if link.kind === 'unsafe'}
102
83
  {@render render_children(node.children)}
103
- </ol>
104
- {:else}
105
- <ul>{@render render_children(node.children)}</ul>
106
- {/if}
107
- {:else if node.type === 'ListItem'}
108
- <li>{@render render_children(node.children)}</li>
109
- {:else if node.type === 'Blockquote'}
110
- <blockquote>{@render render_children(node.children)}</blockquote>
111
- {:else if node.type === 'Table'}
112
- {@const header_rows = node.children.filter((r) => r.header)}
113
- {@const body_rows = node.children.filter((r) => !r.header)}
114
- <table>
115
- {#if header_rows.length > 0}
116
- <thead>
117
- {#each header_rows as row (row)}
118
- <tr>{@render render_cells(row, 'th', node.align)}</tr>
119
- {/each}
120
- </thead>
84
+ {:else if link.kind === 'resolve'}
85
+ <a href={resolve(link.href as any)}>{@render render_children(node.children)}</a>
86
+ {:else if link.kind === 'external'}
87
+ <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
88
+ <a href={link.href} target="_blank" rel="noopener">{@render render_children(node.children)}</a
89
+ >
90
+ {:else}
91
+ <!-- fragment/query/relative/bare references skip resolve() (it accepts only absolute paths) -->
92
+ <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
93
+ <a href={link.href}>{@render render_children(node.children)}</a>
121
94
  {/if}
122
- {#if body_rows.length > 0}
123
- <tbody>
124
- {#each body_rows as row (row)}
125
- <tr>{@render render_cells(row, 'td', node.align)}</tr>
126
- {/each}
127
- </tbody>
95
+ {:else if node.type === 'Paragraph'}
96
+ <p>{@render render_children(node.children)}</p>
97
+ {:else if node.type === 'List'}
98
+ {#if node.ordered}
99
+ <ol start={node.start_number === 1 ? undefined : node.start_number}>
100
+ {@render render_children(node.children)}
101
+ </ol>
102
+ {:else}
103
+ <ul>{@render render_children(node.children)}</ul>
104
+ {/if}
105
+ {:else if node.type === 'ListItem'}
106
+ <li>{@render render_children(node.children)}</li>
107
+ {:else if node.type === 'Blockquote'}
108
+ <blockquote>{@render render_children(node.children)}</blockquote>
109
+ {:else if node.type === 'Table'}
110
+ <!-- by grammar the header row is always first (and the only one) — read
111
+ it by index and skip it per-row in the body, rather than filtering
112
+ twice (matches the streaming view, where a filter would go O(n²)
113
+ across a streamed table) -->
114
+ {@const header_row = node.children[0]?.header ? node.children[0] : undefined}
115
+ <table>
116
+ {#if header_row}
117
+ <thead>
118
+ <tr>{@render render_cells(header_row, 'th', node.align)}</tr>
119
+ </thead>
120
+ {/if}
121
+ {#if node.children.length > (header_row ? 1 : 0)}
122
+ <tbody>
123
+ {#each node.children as row, i (i)}
124
+ {#if !row.header}
125
+ <tr>{@render render_cells(row, 'td', node.align)}</tr>
126
+ {/if}
127
+ {/each}
128
+ </tbody>
129
+ {/if}
130
+ </table>
131
+ {:else if node.type === 'Hr'}
132
+ <hr />
133
+ {:else if node.type === 'Heading'}
134
+ <svelte:element this={`h${node.level}`} id={node.id || undefined}>
135
+ {@render render_children(node.children)}
136
+ </svelte:element>
137
+ {:else if node.type === 'Codeblock'}
138
+ {@const Codeblock = get_codeblock?.()}
139
+ {#if Codeblock}
140
+ <Codeblock lang={node.lang} content={node.content} />
141
+ {:else}
142
+ <pre><code>{node.content}</code></pre>
128
143
  {/if}
129
- </table>
130
- {:else if node.type === 'Hr'}
131
- <hr />
132
- {:else if node.type === 'Heading'}
133
- <svelte:element this={`h${node.level}`} id={node.id || undefined}>
134
- {@render render_children(node.children)}
135
- </svelte:element>
136
- {:else if node.type === 'Codeblock'}
137
- {@const Codeblock = get_codeblock?.()}
138
- {#if Codeblock}
139
- <Codeblock lang={node.lang} content={node.content} />
140
- {:else}
141
- <pre><code>{node.content}</code></pre>
142
144
  {/if}
143
- {/if}
145
+ {/snippet}
144
146
 
145
147
  {#snippet render_children(nodes: Array<MdzNode>)}
146
- {#each nodes as node (node)}
147
- <MdzNodeView {node} />
148
+ <!-- index keys, not object identity: `mdz_parse` yields a fresh node graph
149
+ on every `content` change, so identity keys never match and Svelte rebuilds
150
+ the whole subtree; positional keys let it patch in place. (Sync nodes have
151
+ no stable id; the stream view keys by `node.id`.) -->
152
+ {#each nodes as node, i (i)}
153
+ {@render render_node(node)}
148
154
  {/each}
149
155
  {/snippet}
150
156
 
@@ -1,7 +1,11 @@
1
1
  import type { MdzNode } from './mdz.js';
2
- import MdzNodeView from './MdzNodeView.svelte';
3
- type $$ComponentProps = {
2
+ type $$ComponentProps = // exactly one of `node`/`nodes` — the array form renders a whole tree
3
+ {
4
4
  node: MdzNode;
5
+ nodes?: undefined;
6
+ } | {
7
+ node?: undefined;
8
+ nodes: Array<MdzNode>;
5
9
  };
6
10
  declare const MdzNodeView: import("svelte").Component<$$ComponentProps, {}, "">;
7
11
  type MdzNodeView = ReturnType<typeof MdzNodeView>;
@@ -1 +1 @@
1
- {"version":3,"file":"MdzNodeView.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/MdzNodeView.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,OAAO,EAAiC,MAAM,UAAU,CAAC;AAMtE,OAAO,WAAW,MAAM,sBAAsB,CAAC;AAS9C,KAAK,gBAAgB,GAAI;IACxB,IAAI,EAAE,OAAO,CAAC;CACd,CAAC;AA+JH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MdzNodeView.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/MdzNodeView.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,OAAO,EAAiC,MAAM,UAAU,CAAC;AAUrE,KAAK,gBAAgB,GAEpB,AAFwB,sEAAsE;AAE9F;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAC,GAAG;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC;AAgKjF,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -31,7 +31,7 @@
31
31
  </script>
32
32
 
33
33
  <svelte:element this={inline ? 'span' : 'div'} {...rest} style:white-space={whitespace}>
34
- {#each stream.root as node (node.id)}
35
- <MdzStreamNodeView {node} />
36
- {/each}
34
+ <!-- the array form renders the whole stream through one MdzStreamNodeView
35
+ instance (one set of context reads) instead of one per top-level block -->
36
+ <MdzStreamNodeView nodes={stream.root} />
37
37
  </svelte:element>
@@ -1 +1 @@
1
- {"version":3,"file":"MdzStream.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/MdzStream.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAGL,KAAK,aAAa,EAClB,MAAM,mBAAmB,CAAC;AAC5B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAEhE,KAAK,gBAAgB,GAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,GAAG;IACnF,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AA2BH,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"MdzStream.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/MdzStream.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAGL,KAAK,aAAa,EAClB,MAAM,mBAAmB,CAAC;AAC5B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAEhE,KAAK,gBAAgB,GAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,GAAG;IACnF,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AA0BH,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -1,12 +1,7 @@
1
1
  <script lang="ts">
2
2
  import {resolve} from '$app/paths';
3
3
 
4
- import {
5
- mdz_resolve_relative_path,
6
- mdz_is_safe_reference,
7
- mdz_is_void_element,
8
- } from './mdz_helpers.js';
9
- import MdzStreamNodeView from './MdzStreamNodeView.svelte';
4
+ import {mdz_classify_link, mdz_is_void_element} from './mdz_helpers.js';
10
5
  import {
11
6
  mdz_components_context,
12
7
  mdz_elements_context,
@@ -19,9 +14,11 @@
19
14
 
20
15
  const {
21
16
  node,
22
- }: {
23
- node: MdzStreamNode;
24
- } = $props();
17
+ nodes,
18
+ }: // exactly one of `node`/`nodes` — the array form renders a whole tree
19
+ // through this single component instance (used by `MdzStream`)
20
+ {node: MdzStreamNode; nodes?: undefined} | {node?: undefined; nodes: Array<MdzStreamNode>} =
21
+ $props();
25
22
 
26
23
  const get_components = mdz_components_context.get_maybe();
27
24
  const get_elements = mdz_elements_context.get_maybe();
@@ -38,125 +35,131 @@
38
35
  n.content || n.children.map((c) => c.content).join('');
39
36
  </script>
40
37
 
41
- {#if node.type === 'Element'}
42
- {@const element_config = get_elements?.()?.get(node.name ?? '')}
43
- {#if element_config !== undefined}
44
- {#if mdz_is_void_element(node.name ?? '')}
45
- <!-- void elements cannot have content — any parsed children are dropped -->
46
- <svelte:element this={node.name} />
38
+ <!-- The tree renders through recursive snippets rather than recursive
39
+ component instances — one component (and one set of context reads) per
40
+ tree instead of per node. -->
41
+ {#if nodes}
42
+ {@render render_children(nodes)}
43
+ {:else if node}
44
+ {@render render_node(node)}
45
+ {/if}
46
+
47
+ {#snippet render_node(node: MdzStreamNode)}
48
+ {#if node.type === 'Element'}
49
+ {@const element_config = get_elements?.()?.get(node.name ?? '')}
50
+ {#if element_config !== undefined}
51
+ {#if mdz_is_void_element(node.name ?? '')}
52
+ <!-- void elements cannot have content — any parsed children are dropped -->
53
+ <svelte:element this={node.name} />
54
+ {:else}
55
+ <svelte:element this={node.name}>
56
+ {#if node.children.length > 0}
57
+ {@render render_children(node.children)}
58
+ {/if}
59
+ </svelte:element>
60
+ {/if}
47
61
  {:else}
48
- <svelte:element this={node.name}>
62
+ {@render render_unregistered_tag(node.name ?? '', node.children)}
63
+ {/if}
64
+ {:else if node.type === 'Component'}
65
+ {@const Component = get_components?.()?.get(node.name ?? '')}
66
+ {#if Component}
67
+ <Component>
49
68
  {#if node.children.length > 0}
50
69
  {@render render_children(node.children)}
51
70
  {/if}
52
- </svelte:element>
71
+ </Component>
72
+ {:else}
73
+ {@render render_unregistered_tag(node.name ?? '', node.children)}
53
74
  {/if}
54
- {:else}
55
- {@render render_unregistered_tag(node.name ?? '', node.children)}
56
- {/if}
57
- {:else if node.type === 'Component'}
58
- {@const Component = get_components?.()?.get(node.name ?? '')}
59
- {#if Component}
60
- <Component>
61
- {#if node.children.length > 0}
62
- {@render render_children(node.children)}
63
- {/if}
64
- </Component>
65
- {:else}
66
- {@render render_unregistered_tag(node.name ?? '', node.children)}
67
- {/if}
68
- {:else if node.type === 'Text'}
69
- {node.content}
70
- {:else if node.type === 'Code'}
71
- {@const Code = get_code?.()}
72
- {@const code_content = streaming_content(node)}
73
- {#if Code}
74
- <Code reference={code_content} />
75
- {:else}
76
- <code>{code_content}</code>
77
- {/if}
78
- {:else if node.type === 'Bold'}
79
- <strong>{@render render_children(node.children)}</strong>
80
- {:else if node.type === 'Italic'}
81
- <em>{@render render_children(node.children)}</em>
82
- {:else if node.type === 'Strikethrough'}
83
- <s>{@render render_children(node.children)}</s>
84
- {:else if node.type === 'Link'}
85
- {@const reference = node.reference ?? ''}
86
- {#if !mdz_is_safe_reference(reference)}
87
- {@render render_children(node.children)}
88
- {:else if node.link_type === 'internal'}
89
- {@const skip_resolve = reference.startsWith('#') || reference.startsWith('?')}
90
- {@const mdz_base = get_mdz_base?.()}
91
- {#if reference.startsWith('.') && mdz_base}
92
- {@const resolved = mdz_resolve_relative_path(reference, mdz_base)}
93
- <a href={resolve(resolved as any)}>{@render render_children(node.children)}</a>
94
- {:else if skip_resolve || reference.startsWith('.') || !reference.startsWith('/')}
95
- <!-- Fragment, query, and relative links (including bare references like `foo`) skip resolve() -->
96
- <!-- resolve() only accepts absolute pathnames or route IDs and throws on anything else -->
97
- <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
98
- <a href={reference}>{@render render_children(node.children)}</a>
75
+ {:else if node.type === 'Text'}
76
+ {node.content}
77
+ {:else if node.type === 'Code'}
78
+ {@const Code = get_code?.()}
79
+ {@const code_content = streaming_content(node)}
80
+ {#if Code}
81
+ <Code reference={code_content} />
99
82
  {:else}
100
- <a href={resolve(reference as any)}>{@render render_children(node.children)}</a>
83
+ <code>{code_content}</code>
101
84
  {/if}
102
- {:else}
103
- <!-- external link -->
104
- <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
105
- <a href={reference} target="_blank" rel="noopener">{@render render_children(node.children)}</a>
106
- {/if}
107
- {:else if node.type === 'Paragraph'}
108
- <p>{@render render_children(node.children)}</p>
109
- {:else if node.type === 'List'}
110
- {#if node.ordered}
111
- <ol start={node.start_number === 1 ? undefined : node.start_number}>
85
+ {:else if node.type === 'Bold'}
86
+ <strong>{@render render_children(node.children)}</strong>
87
+ {:else if node.type === 'Italic'}
88
+ <em>{@render render_children(node.children)}</em>
89
+ {:else if node.type === 'Strikethrough'}
90
+ <s>{@render render_children(node.children)}</s>
91
+ {:else if node.type === 'Link'}
92
+ {@const link = mdz_classify_link(node.reference ?? '', node.link_type, get_mdz_base?.())}
93
+ {#if link.kind === 'unsafe'}
112
94
  {@render render_children(node.children)}
113
- </ol>
114
- {:else}
115
- <ul>{@render render_children(node.children)}</ul>
116
- {/if}
117
- {:else if node.type === 'ListItem'}
118
- <li>{@render render_children(node.children)}</li>
119
- {:else if node.type === 'Blockquote'}
120
- <blockquote>{@render render_children(node.children)}</blockquote>
121
- {:else if node.type === 'Table'}
122
- {@const align = node.align ?? []}
123
- {@const header_rows = node.children.filter((r) => r.header)}
124
- {@const body_rows = node.children.filter((r) => !r.header)}
125
- <table>
126
- {#if header_rows.length > 0}
127
- <thead>
128
- {#each header_rows as row (row.id)}
129
- <tr>{@render render_cells(row, 'th', align)}</tr>
130
- {/each}
131
- </thead>
95
+ {:else if link.kind === 'resolve'}
96
+ <a href={resolve(link.href as any)}>{@render render_children(node.children)}</a>
97
+ {:else if link.kind === 'external'}
98
+ <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
99
+ <a href={link.href} target="_blank" rel="noopener">{@render render_children(node.children)}</a
100
+ >
101
+ {:else}
102
+ <!-- fragment/query/relative/bare references skip resolve() (it accepts only absolute paths) -->
103
+ <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
104
+ <a href={link.href}>{@render render_children(node.children)}</a>
132
105
  {/if}
133
- {#if body_rows.length > 0}
134
- <tbody>
135
- {#each body_rows as row (row.id)}
136
- <tr>{@render render_cells(row, 'td', align)}</tr>
137
- {/each}
138
- </tbody>
106
+ {:else if node.type === 'Paragraph'}
107
+ <p>{@render render_children(node.children)}</p>
108
+ {:else if node.type === 'List'}
109
+ {#if node.ordered}
110
+ <ol start={node.start_number === 1 ? undefined : node.start_number}>
111
+ {@render render_children(node.children)}
112
+ </ol>
113
+ {:else}
114
+ <ul>{@render render_children(node.children)}</ul>
115
+ {/if}
116
+ {:else if node.type === 'ListItem'}
117
+ <li>{@render render_children(node.children)}</li>
118
+ {:else if node.type === 'Blockquote'}
119
+ <blockquote>{@render render_children(node.children)}</blockquote>
120
+ {:else if node.type === 'Table'}
121
+ {@const align = node.align ?? []}
122
+ <!-- by grammar the header row is always first (and the only one) — read
123
+ it by index and skip it per-row in the body, rather than filtering: a
124
+ filter re-partitions every prior row on each streamed row append,
125
+ going O(n²) across a streamed table -->
126
+ {@const header_row = node.children[0]?.header ? node.children[0] : undefined}
127
+ <table>
128
+ {#if header_row}
129
+ <thead>
130
+ <tr>{@render render_cells(header_row, 'th', align)}</tr>
131
+ </thead>
132
+ {/if}
133
+ {#if node.children.length > (header_row ? 1 : 0)}
134
+ <tbody>
135
+ {#each node.children as row (row.id)}
136
+ {#if !row.header}
137
+ <tr>{@render render_cells(row, 'td', align)}</tr>
138
+ {/if}
139
+ {/each}
140
+ </tbody>
141
+ {/if}
142
+ </table>
143
+ {:else if node.type === 'Hr'}
144
+ <hr />
145
+ {:else if node.type === 'Heading'}
146
+ <svelte:element this={`h${node.level}`} id={node.heading_id || undefined}>
147
+ {@render render_children(node.children)}
148
+ </svelte:element>
149
+ {:else if node.type === 'Codeblock'}
150
+ {@const Codeblock = get_codeblock?.()}
151
+ {@const codeblock_content = streaming_content(node)}
152
+ {#if Codeblock}
153
+ <Codeblock lang={node.lang ?? null} content={codeblock_content} />
154
+ {:else}
155
+ <pre><code>{codeblock_content}</code></pre>
139
156
  {/if}
140
- </table>
141
- {:else if node.type === 'Hr'}
142
- <hr />
143
- {:else if node.type === 'Heading'}
144
- <svelte:element this={`h${node.level}`} id={node.heading_id || undefined}>
145
- {@render render_children(node.children)}
146
- </svelte:element>
147
- {:else if node.type === 'Codeblock'}
148
- {@const Codeblock = get_codeblock?.()}
149
- {@const codeblock_content = streaming_content(node)}
150
- {#if Codeblock}
151
- <Codeblock lang={node.lang ?? null} content={codeblock_content} />
152
- {:else}
153
- <pre><code>{codeblock_content}</code></pre>
154
157
  {/if}
155
- {/if}
158
+ {/snippet}
156
159
 
157
160
  {#snippet render_children(nodes: Array<MdzStreamNode>)}
158
161
  {#each nodes as node (node.id)}
159
- <MdzStreamNodeView {node} />
162
+ {@render render_node(node)}
160
163
  {/each}
161
164
  {/snippet}
162
165