@fuzdev/mdz 0.1.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 (77) hide show
  1. package/README.md +4 -0
  2. package/dist/Mdz.svelte +15 -7
  3. package/dist/Mdz.svelte.d.ts +9 -2
  4. package/dist/Mdz.svelte.d.ts.map +1 -1
  5. package/dist/MdzNodeView.svelte +130 -95
  6. package/dist/MdzNodeView.svelte.d.ts +6 -2
  7. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  8. package/dist/MdzStream.svelte +3 -3
  9. package/dist/MdzStream.svelte.d.ts.map +1 -1
  10. package/dist/MdzStreamNodeView.svelte +129 -95
  11. package/dist/MdzStreamNodeView.svelte.d.ts +6 -2
  12. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
  13. package/dist/context_helpers.d.ts +1 -1
  14. package/dist/mdz.d.ts +59 -2
  15. package/dist/mdz.d.ts.map +1 -1
  16. package/dist/mdz.js +37 -1
  17. package/dist/mdz_debug_work.d.ts +38 -0
  18. package/dist/mdz_debug_work.d.ts.map +1 -0
  19. package/dist/mdz_debug_work.js +37 -0
  20. package/dist/mdz_helpers.d.ts +96 -1
  21. package/dist/mdz_helpers.d.ts.map +1 -1
  22. package/dist/mdz_helpers.js +192 -0
  23. package/dist/mdz_lexer.d.ts +47 -1
  24. package/dist/mdz_lexer.d.ts.map +1 -1
  25. package/dist/mdz_lexer.js +364 -15
  26. package/dist/mdz_opcodes.d.ts +6 -1
  27. package/dist/mdz_opcodes.d.ts.map +1 -1
  28. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -1
  29. package/dist/mdz_opcodes_to_nodes.js +26 -0
  30. package/dist/mdz_stream_parser.d.ts.map +1 -1
  31. package/dist/mdz_stream_parser.js +20 -1
  32. package/dist/mdz_stream_parser_inline.d.ts.map +1 -1
  33. package/dist/mdz_stream_parser_inline.js +19 -17
  34. package/dist/mdz_stream_parser_link.d.ts.map +1 -1
  35. package/dist/mdz_stream_parser_link.js +88 -56
  36. package/dist/mdz_stream_parser_list.d.ts.map +1 -1
  37. package/dist/mdz_stream_parser_list.js +32 -3
  38. package/dist/mdz_stream_parser_state.d.ts +41 -0
  39. package/dist/mdz_stream_parser_state.d.ts.map +1 -1
  40. package/dist/mdz_stream_parser_state.js +53 -3
  41. package/dist/mdz_stream_parser_table.d.ts +73 -0
  42. package/dist/mdz_stream_parser_table.d.ts.map +1 -0
  43. package/dist/mdz_stream_parser_table.js +362 -0
  44. package/dist/mdz_stream_parser_text.d.ts.map +1 -1
  45. package/dist/mdz_stream_parser_text.js +35 -24
  46. package/dist/mdz_stream_state.svelte.d.ts +5 -0
  47. package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
  48. package/dist/mdz_stream_state.svelte.js +8 -0
  49. package/dist/mdz_to_svelte.d.ts.map +1 -1
  50. package/dist/mdz_to_svelte.js +51 -20
  51. package/dist/mdz_token_parser.d.ts +16 -0
  52. package/dist/mdz_token_parser.d.ts.map +1 -1
  53. package/dist/mdz_token_parser.js +112 -14
  54. package/dist/svelte_preprocess_mdz.js +33 -39
  55. package/package.json +11 -8
  56. package/src/lib/context_helpers.ts +1 -1
  57. package/src/lib/mdz.ts +79 -3
  58. package/src/lib/mdz_contexts.ts +1 -1
  59. package/src/lib/mdz_debug_work.ts +38 -0
  60. package/src/lib/mdz_helpers.ts +222 -1
  61. package/src/lib/mdz_lexer.ts +420 -21
  62. package/src/lib/mdz_opcodes.ts +10 -1
  63. package/src/lib/mdz_opcodes_to_nodes.ts +37 -3
  64. package/src/lib/mdz_stream_parser.ts +26 -9
  65. package/src/lib/mdz_stream_parser_block.ts +2 -2
  66. package/src/lib/mdz_stream_parser_inline.ts +25 -21
  67. package/src/lib/mdz_stream_parser_link.ts +92 -59
  68. package/src/lib/mdz_stream_parser_list.ts +34 -6
  69. package/src/lib/mdz_stream_parser_state.ts +87 -5
  70. package/src/lib/mdz_stream_parser_table.ts +420 -0
  71. package/src/lib/mdz_stream_parser_text.ts +40 -39
  72. package/src/lib/mdz_stream_parser_url.ts +2 -2
  73. package/src/lib/mdz_stream_state.svelte.ts +8 -1
  74. package/src/lib/mdz_to_svelte.ts +49 -23
  75. package/src/lib/mdz_token_parser.ts +135 -21
  76. package/src/lib/svelte_preprocess_mdz.ts +37 -38
  77. package/src/lib/tsdoc_mdz.ts +1 -1
package/README.md CHANGED
@@ -27,6 +27,10 @@ For more see the [docs](https://mdz.fuz.dev/docs).
27
27
  AI disclosure: almost everything here was generated by machine agents except
28
28
  for the designs and intros. mdz is the result of 5+ years of prototyping ideas.
29
29
 
30
+ > ⚠️ This project is pre-stable: breaking changes are expected and
31
+ > some features (like attributes) are not yet implemented.
32
+ > Feedback is welcome!
33
+
30
34
  ## License
31
35
 
32
36
  [MIT](LICENSE)
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"}
@@ -1,13 +1,8 @@
1
1
  <script lang="ts">
2
2
  import {resolve} from '$app/paths';
3
3
 
4
- import type {MdzNode} 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';
4
+ import type {MdzNode, MdzNodeTableRow, MdzTableAlign} from './mdz.js';
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,103 +25,142 @@
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 -->
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} />
71
+ {:else}
72
+ <code>{node.content}</code>
73
+ {/if}
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'}
83
+ {@render render_children(node.children)}
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
87
  <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
88
- <a href={reference}>{@render render_children(node.children)}</a>
88
+ <a href={link.href} target="_blank" rel="noopener">{@render render_children(node.children)}</a
89
+ >
89
90
  {:else}
90
- <a href={resolve(reference as any)}>{@render render_children(node.children)}</a>
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>
91
94
  {/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}>
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}>
102
135
  {@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 === 'Hr'}
112
- <hr />
113
- {:else if node.type === 'Heading'}
114
- <svelte:element this={`h${node.level}`} id={node.id || undefined}>
115
- {@render render_children(node.children)}
116
- </svelte:element>
117
- {:else if node.type === 'Codeblock'}
118
- {@const Codeblock = get_codeblock?.()}
119
- {#if Codeblock}
120
- <Codeblock lang={node.lang} content={node.content} />
121
- {:else}
122
- <pre><code>{node.content}</code></pre>
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>
143
+ {/if}
123
144
  {/if}
124
- {/if}
145
+ {/snippet}
125
146
 
126
147
  {#snippet render_children(nodes: Array<MdzNode>)}
127
- {#each nodes as node (node)}
128
- <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)}
154
+ {/each}
155
+ {/snippet}
156
+
157
+ {#snippet render_cells(row: MdzNodeTableRow, tag: 'th' | 'td', align: Array<MdzTableAlign>)}
158
+ <!-- normalize to the column count: pad short rows, ignore extra cells -->
159
+ {#each align as a, i (i)}
160
+ {@const cell = row.children[i]}
161
+ <svelte:element this={tag} style={a ? `text-align:${a}` : undefined}>
162
+ {#if cell}{@render render_children(cell.children)}{/if}
163
+ </svelte:element>
129
164
  {/each}
130
165
  {/snippet}
131
166
 
@@ -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,EAAC,MAAM,UAAU,CAAC;AAMtC,OAAO,WAAW,MAAM,sBAAsB,CAAC;AAS9C,KAAK,gBAAgB,GAAI;IACxB,IAAI,EAAE,OAAO,CAAC;CACd,CAAC;AAkIH,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,
@@ -15,12 +10,15 @@
15
10
  mdz_codeblock_context,
16
11
  } from './mdz_contexts.js';
17
12
  import type {MdzStreamNode} from './mdz_stream_state.svelte.js';
13
+ import type {MdzTableAlign} from './mdz.js';
18
14
 
19
15
  const {
20
16
  node,
21
- }: {
22
- node: MdzStreamNode;
23
- } = $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();
24
22
 
25
23
  const get_components = mdz_components_context.get_maybe();
26
24
  const get_elements = mdz_elements_context.get_maybe();
@@ -37,105 +35,141 @@
37
35
  n.content || n.children.map((c) => c.content).join('');
38
36
  </script>
39
37
 
40
- {#if node.type === 'Element'}
41
- {@const element_config = get_elements?.()?.get(node.name ?? '')}
42
- {#if element_config !== undefined}
43
- {#if mdz_is_void_element(node.name ?? '')}
44
- <!-- void elements cannot have content — any parsed children are dropped -->
45
- <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}
46
61
  {:else}
47
- <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>
48
68
  {#if node.children.length > 0}
49
69
  {@render render_children(node.children)}
50
70
  {/if}
51
- </svelte:element>
71
+ </Component>
72
+ {:else}
73
+ {@render render_unregistered_tag(node.name ?? '', node.children)}
52
74
  {/if}
53
- {:else}
54
- {@render render_unregistered_tag(node.name ?? '', node.children)}
55
- {/if}
56
- {:else if node.type === 'Component'}
57
- {@const Component = get_components?.()?.get(node.name ?? '')}
58
- {#if Component}
59
- <Component>
60
- {#if node.children.length > 0}
61
- {@render render_children(node.children)}
62
- {/if}
63
- </Component>
64
- {:else}
65
- {@render render_unregistered_tag(node.name ?? '', node.children)}
66
- {/if}
67
- {:else if node.type === 'Text'}
68
- {node.content}
69
- {:else if node.type === 'Code'}
70
- {@const Code = get_code?.()}
71
- {@const code_content = streaming_content(node)}
72
- {#if Code}
73
- <Code reference={code_content} />
74
- {:else}
75
- <code>{code_content}</code>
76
- {/if}
77
- {:else if node.type === 'Bold'}
78
- <strong>{@render render_children(node.children)}</strong>
79
- {:else if node.type === 'Italic'}
80
- <em>{@render render_children(node.children)}</em>
81
- {:else if node.type === 'Strikethrough'}
82
- <s>{@render render_children(node.children)}</s>
83
- {:else if node.type === 'Link'}
84
- {@const reference = node.reference ?? ''}
85
- {#if !mdz_is_safe_reference(reference)}
86
- {@render render_children(node.children)}
87
- {:else if node.link_type === 'internal'}
88
- {@const skip_resolve = reference.startsWith('#') || reference.startsWith('?')}
89
- {@const mdz_base = get_mdz_base?.()}
90
- {#if reference.startsWith('.') && mdz_base}
91
- {@const resolved = mdz_resolve_relative_path(reference, mdz_base)}
92
- <a href={resolve(resolved as any)}>{@render render_children(node.children)}</a>
93
- {:else if skip_resolve || reference.startsWith('.') || !reference.startsWith('/')}
94
- <!-- Fragment, query, and relative links (including bare references like `foo`) skip resolve() -->
95
- <!-- resolve() only accepts absolute pathnames or route IDs and throws on anything else -->
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} />
82
+ {:else}
83
+ <code>{code_content}</code>
84
+ {/if}
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'}
94
+ {@render render_children(node.children)}
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'}
96
98
  <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
97
- <a href={reference}>{@render render_children(node.children)}</a>
99
+ <a href={link.href} target="_blank" rel="noopener">{@render render_children(node.children)}</a
100
+ >
98
101
  {:else}
99
- <a href={resolve(reference as any)}>{@render render_children(node.children)}</a>
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>
100
105
  {/if}
101
- {:else}
102
- <!-- external link -->
103
- <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
104
- <a href={reference} target="_blank" rel="noopener">{@render render_children(node.children)}</a>
105
- {/if}
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}>
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}>
111
147
  {@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 === 'Hr'}
121
- <hr />
122
- {:else if node.type === 'Heading'}
123
- <svelte:element this={`h${node.level}`} id={node.heading_id || undefined}>
124
- {@render render_children(node.children)}
125
- </svelte:element>
126
- {:else if node.type === 'Codeblock'}
127
- {@const Codeblock = get_codeblock?.()}
128
- {@const codeblock_content = streaming_content(node)}
129
- {#if Codeblock}
130
- <Codeblock lang={node.lang ?? null} content={codeblock_content} />
131
- {:else}
132
- <pre><code>{codeblock_content}</code></pre>
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>
156
+ {/if}
133
157
  {/if}
134
- {/if}
158
+ {/snippet}
135
159
 
136
160
  {#snippet render_children(nodes: Array<MdzStreamNode>)}
137
161
  {#each nodes as node (node.id)}
138
- <MdzStreamNodeView {node} />
162
+ {@render render_node(node)}
163
+ {/each}
164
+ {/snippet}
165
+
166
+ {#snippet render_cells(row: MdzStreamNode, tag: 'th' | 'td', align: Array<MdzTableAlign>)}
167
+ <!-- normalize to the column count: pad short rows, ignore extra cells -->
168
+ {#each align as a, i (i)}
169
+ {@const cell = row.children[i]}
170
+ <svelte:element this={tag} style={a ? `text-align:${a}` : undefined}>
171
+ {#if cell}{@render render_children(cell.children)}{/if}
172
+ </svelte:element>
139
173
  {/each}
140
174
  {/snippet}
141
175