@fuzdev/mdz 0.2.0 → 0.4.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.
- package/README.md +4 -0
- package/dist/Mdz.svelte +18 -9
- package/dist/Mdz.svelte.d.ts +9 -2
- package/dist/Mdz.svelte.d.ts.map +1 -1
- package/dist/MdzNodeView.svelte +129 -115
- package/dist/MdzNodeView.svelte.d.ts +4 -1
- package/dist/MdzNodeView.svelte.d.ts.map +1 -1
- package/dist/MdzPrecompiled.svelte +3 -3
- package/dist/MdzPrecompiled.svelte.d.ts.map +1 -1
- package/dist/MdzRoot.svelte +3 -3
- package/dist/MdzRoot.svelte.d.ts.map +1 -1
- package/dist/MdzStream.svelte +6 -6
- package/dist/MdzStream.svelte.d.ts.map +1 -1
- package/dist/MdzStreamNodeView.svelte +129 -118
- package/dist/MdzStreamNodeView.svelte.d.ts +4 -1
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
- package/dist/context_helpers.js +1 -1
- package/dist/mdz.d.ts +6 -0
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +8 -0
- package/dist/mdz_contexts.d.ts.map +1 -1
- package/dist/mdz_debug_work.d.ts +38 -0
- package/dist/mdz_debug_work.d.ts.map +1 -0
- package/dist/mdz_debug_work.js +37 -0
- package/dist/mdz_helpers.d.ts +61 -0
- package/dist/mdz_helpers.d.ts.map +1 -1
- package/dist/mdz_helpers.js +74 -2
- package/dist/mdz_lexer.d.ts +12 -0
- package/dist/mdz_lexer.d.ts.map +1 -1
- package/dist/mdz_lexer.js +190 -40
- package/dist/mdz_opcodes.d.ts.map +1 -1
- package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -1
- package/dist/mdz_opcodes_to_nodes.js +22 -17
- package/dist/mdz_stream_parser.d.ts.map +1 -1
- package/dist/mdz_stream_parser.js +8 -8
- package/dist/mdz_stream_parser_block.js +5 -5
- package/dist/mdz_stream_parser_inline.d.ts.map +1 -1
- package/dist/mdz_stream_parser_inline.js +22 -20
- package/dist/mdz_stream_parser_link.d.ts.map +1 -1
- package/dist/mdz_stream_parser_link.js +88 -56
- package/dist/mdz_stream_parser_list.d.ts.map +1 -1
- package/dist/mdz_stream_parser_list.js +23 -9
- package/dist/mdz_stream_parser_state.d.ts +29 -0
- package/dist/mdz_stream_parser_state.d.ts.map +1 -1
- package/dist/mdz_stream_parser_state.js +59 -10
- package/dist/mdz_stream_parser_table.d.ts +3 -2
- package/dist/mdz_stream_parser_table.d.ts.map +1 -1
- package/dist/mdz_stream_parser_table.js +24 -13
- package/dist/mdz_stream_parser_text.d.ts.map +1 -1
- package/dist/mdz_stream_parser_text.js +36 -25
- package/dist/mdz_stream_parser_url.js +7 -7
- package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
- package/dist/mdz_to_svelte.d.ts +1 -1
- package/dist/mdz_to_svelte.d.ts.map +1 -1
- package/dist/mdz_to_svelte.js +28 -31
- package/dist/mdz_token_parser.d.ts.map +1 -1
- package/dist/mdz_token_parser.js +24 -25
- package/dist/svelte_preprocess_mdz.d.ts +2 -2
- package/dist/svelte_preprocess_mdz.d.ts.map +1 -1
- package/dist/svelte_preprocess_mdz.js +41 -47
- package/package.json +28 -40
- package/src/lib/context_helpers.ts +2 -2
- package/src/lib/mdz.ts +11 -2
- package/src/lib/mdz_contexts.ts +5 -5
- package/src/lib/mdz_debug_work.ts +38 -0
- package/src/lib/mdz_helpers.ts +126 -23
- package/src/lib/mdz_lexer.ts +243 -93
- package/src/lib/mdz_opcodes.ts +1 -1
- package/src/lib/mdz_opcodes_to_nodes.ts +35 -30
- package/src/lib/mdz_stream_parser.ts +17 -17
- package/src/lib/mdz_stream_parser_block.ts +14 -14
- package/src/lib/mdz_stream_parser_inline.ts +38 -34
- package/src/lib/mdz_stream_parser_link.ts +99 -66
- package/src/lib/mdz_stream_parser_list.ts +41 -29
- package/src/lib/mdz_stream_parser_state.ts +100 -28
- package/src/lib/mdz_stream_parser_table.ts +47 -36
- package/src/lib/mdz_stream_parser_text.ts +40 -39
- package/src/lib/mdz_stream_parser_url.ts +11 -11
- package/src/lib/mdz_stream_state.svelte.ts +8 -8
- package/src/lib/mdz_to_svelte.ts +38 -46
- package/src/lib/mdz_token_parser.ts +55 -58
- package/src/lib/svelte_preprocess_mdz.ts +92 -89
- package/src/lib/tsdoc_mdz.ts +1 -1
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
is_valid_path_char,
|
|
24
24
|
is_word_char,
|
|
25
25
|
match_url_prefix_case_insensitive,
|
|
26
|
-
mdz_trim_url_punctuation
|
|
26
|
+
mdz_trim_url_punctuation
|
|
27
27
|
} from './mdz_helpers.ts';
|
|
28
28
|
import {
|
|
29
29
|
type MdzStreamParserState,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
emit,
|
|
34
34
|
ensure_paragraph,
|
|
35
35
|
flush_text,
|
|
36
|
-
offset
|
|
36
|
+
offset
|
|
37
37
|
} from './mdz_stream_parser_state.ts';
|
|
38
38
|
|
|
39
39
|
const HTTPS_PREFIX = 'https://';
|
|
@@ -136,7 +136,7 @@ export const try_auto_path_relative = (state: MdzStreamParserState, forced = fal
|
|
|
136
136
|
*/
|
|
137
137
|
export const start_pending_url = (
|
|
138
138
|
state: MdzStreamParserState,
|
|
139
|
-
link_type: 'external' | 'internal'
|
|
139
|
+
link_type: 'external' | 'internal'
|
|
140
140
|
): void => {
|
|
141
141
|
flush_text(state);
|
|
142
142
|
ensure_paragraph(state);
|
|
@@ -147,7 +147,7 @@ export const start_pending_url = (
|
|
|
147
147
|
link_type,
|
|
148
148
|
confirmed: true,
|
|
149
149
|
viable_https: false,
|
|
150
|
-
viable_http: false
|
|
150
|
+
viable_http: false
|
|
151
151
|
};
|
|
152
152
|
// don't advance pos — URL chars start at current position, handled by process_url_content
|
|
153
153
|
};
|
|
@@ -169,7 +169,7 @@ export const start_speculative_url = (state: MdzStreamParserState): void => {
|
|
|
169
169
|
link_type: 'external',
|
|
170
170
|
confirmed: false,
|
|
171
171
|
viable_https: true,
|
|
172
|
-
viable_http: true
|
|
172
|
+
viable_http: true
|
|
173
173
|
};
|
|
174
174
|
};
|
|
175
175
|
|
|
@@ -313,7 +313,7 @@ export const complete_pending_url = (state: MdzStreamParserState): void => {
|
|
|
313
313
|
start: pu.start,
|
|
314
314
|
end: url_end,
|
|
315
315
|
trim_end,
|
|
316
|
-
trim_id
|
|
316
|
+
trim_id
|
|
317
317
|
});
|
|
318
318
|
state.active_text_id = trim_id;
|
|
319
319
|
} else {
|
|
@@ -325,7 +325,7 @@ export const complete_pending_url = (state: MdzStreamParserState): void => {
|
|
|
325
325
|
reference,
|
|
326
326
|
link_type: pu.link_type,
|
|
327
327
|
start: pu.start,
|
|
328
|
-
end: url_end
|
|
328
|
+
end: url_end
|
|
329
329
|
});
|
|
330
330
|
state.active_text_id = null;
|
|
331
331
|
}
|
|
@@ -339,7 +339,7 @@ export const complete_pending_url = (state: MdzStreamParserState): void => {
|
|
|
339
339
|
*/
|
|
340
340
|
export const scan_auto_link_forced = (
|
|
341
341
|
state: MdzStreamParserState,
|
|
342
|
-
link_type: 'external' | 'internal'
|
|
342
|
+
link_type: 'external' | 'internal'
|
|
343
343
|
): TryResult => {
|
|
344
344
|
let i = state.pos;
|
|
345
345
|
while (i < state.buffer.length) {
|
|
@@ -366,7 +366,7 @@ export const scan_auto_link_forced = (
|
|
|
366
366
|
ensure_paragraph(state);
|
|
367
367
|
|
|
368
368
|
const link_id = alloc_id(state);
|
|
369
|
-
emit(state, {type: 'open', id: link_id, node_type: 'Link', start: link_start});
|
|
369
|
+
emit(state, { type: 'open', id: link_id, node_type: 'Link', start: link_start });
|
|
370
370
|
const text_id = alloc_id(state);
|
|
371
371
|
emit(state, {
|
|
372
372
|
type: 'text',
|
|
@@ -374,9 +374,9 @@ export const scan_auto_link_forced = (
|
|
|
374
374
|
content: reference,
|
|
375
375
|
text_type: 'Text',
|
|
376
376
|
start: link_start,
|
|
377
|
-
end: link_end
|
|
377
|
+
end: link_end
|
|
378
378
|
});
|
|
379
|
-
emit(state, {type: 'close', id: link_id, end: link_end, reference, link_type});
|
|
379
|
+
emit(state, { type: 'close', id: link_id, end: link_end, reference, link_type });
|
|
380
380
|
|
|
381
381
|
state.active_text_id = null;
|
|
382
382
|
state.column += end_pos - state.pos;
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {DEV} from 'esm-env';
|
|
11
|
+
import { DEV } from 'esm-env';
|
|
12
12
|
|
|
13
|
-
import type {MdzNodeId, MdzNodeType, MdzOpcode} from './mdz_opcodes.ts';
|
|
14
|
-
import type {MdzTableAlign} from './mdz.ts';
|
|
13
|
+
import type { MdzNodeId, MdzNodeType, MdzOpcode } from './mdz_opcodes.ts';
|
|
14
|
+
import type { MdzTableAlign } from './mdz.ts';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* A reactive node in the stream renderer tree.
|
|
@@ -76,7 +76,7 @@ export class MdzStreamState {
|
|
|
76
76
|
const parent = this.#nodes.get(this.#stack[stack_idx - 1]!);
|
|
77
77
|
if (DEV && !parent) {
|
|
78
78
|
throw new Error(
|
|
79
|
-
`MdzStreamState: parent id ${this.#stack[stack_idx - 1]} on stack but missing from nodes
|
|
79
|
+
`MdzStreamState: parent id ${this.#stack[stack_idx - 1]} on stack but missing from nodes`
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
return parent ? parent.children : this.root;
|
|
@@ -99,7 +99,7 @@ export class MdzStreamState {
|
|
|
99
99
|
case 'open': {
|
|
100
100
|
if (DEV && this.#nodes.has(opcode.id)) {
|
|
101
101
|
throw new Error(
|
|
102
|
-
`MdzStreamState: duplicate open for id ${opcode.id} (node_type '${opcode.node_type}')
|
|
102
|
+
`MdzStreamState: duplicate open for id ${opcode.id} (node_type '${opcode.node_type}')`
|
|
103
103
|
);
|
|
104
104
|
}
|
|
105
105
|
const node = new MdzStreamNode(opcode.id, opcode.node_type);
|
|
@@ -168,7 +168,7 @@ export class MdzStreamState {
|
|
|
168
168
|
case 'text': {
|
|
169
169
|
if (DEV && this.#nodes.has(opcode.id)) {
|
|
170
170
|
throw new Error(
|
|
171
|
-
`MdzStreamState: duplicate text for id ${opcode.id} (text_type '${opcode.text_type}')
|
|
171
|
+
`MdzStreamState: duplicate text for id ${opcode.id} (text_type '${opcode.text_type}')`
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
174
|
const node = new MdzStreamNode(opcode.id, opcode.text_type);
|
|
@@ -215,7 +215,7 @@ export class MdzStreamState {
|
|
|
215
215
|
case 'void': {
|
|
216
216
|
if (DEV && this.#nodes.has(opcode.id)) {
|
|
217
217
|
throw new Error(
|
|
218
|
-
`MdzStreamState: duplicate void for id ${opcode.id} (node_type '${opcode.node_type}')
|
|
218
|
+
`MdzStreamState: duplicate void for id ${opcode.id} (node_type '${opcode.node_type}')`
|
|
219
219
|
);
|
|
220
220
|
}
|
|
221
221
|
const node = new MdzStreamNode(opcode.id, opcode.node_type);
|
|
@@ -255,7 +255,7 @@ export class MdzStreamState {
|
|
|
255
255
|
if (node_idx === -1) {
|
|
256
256
|
if (DEV) {
|
|
257
257
|
throw new Error(
|
|
258
|
-
`MdzStreamState: revert for id ${opcode.id} but node not found in parent's children
|
|
258
|
+
`MdzStreamState: revert for id ${opcode.id} but node not found in parent's children`
|
|
259
259
|
);
|
|
260
260
|
}
|
|
261
261
|
break;
|
package/src/lib/mdz_to_svelte.ts
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {UnreachableError} from '@fuzdev/fuz_util/error.ts';
|
|
12
|
-
import {escape_svelte_text} from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
|
|
13
|
-
import {escape_js_string} from '@fuzdev/fuz_util/string.ts';
|
|
11
|
+
import { UnreachableError } from '@fuzdev/fuz_util/error.ts';
|
|
12
|
+
import { escape_svelte_text } from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
|
|
13
|
+
import { escape_js_string } from '@fuzdev/fuz_util/string.ts';
|
|
14
14
|
|
|
15
|
-
import type {MdzNode, MdzNodeTableRow} from './mdz.ts';
|
|
16
|
-
import {
|
|
15
|
+
import type { MdzNode, MdzNodeTableRow } from './mdz.ts';
|
|
16
|
+
import { mdz_is_void_element, mdz_classify_link } from './mdz_helpers.ts';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Result of converting `MdzNode` arrays to Svelte markup.
|
|
@@ -23,7 +23,7 @@ export interface MdzToSvelteResult {
|
|
|
23
23
|
markup: string;
|
|
24
24
|
|
|
25
25
|
/** Required imports: `Map<local_name, {path, kind}>`. */
|
|
26
|
-
imports: Map<string, {path: string; kind: 'default' | 'named'}>;
|
|
26
|
+
imports: Map<string, { path: string; kind: 'default' | 'named' }>;
|
|
27
27
|
|
|
28
28
|
/** Whether content references unconfigured Component or Element tags. */
|
|
29
29
|
has_unconfigured_tags: boolean;
|
|
@@ -38,7 +38,7 @@ const component_local_name = (import_path: string): string =>
|
|
|
38
38
|
*/
|
|
39
39
|
export interface MdzToSvelteOptions {
|
|
40
40
|
/**
|
|
41
|
-
* Component name to import path mapping (e.g., `{Alert: '
|
|
41
|
+
* Component name to import path mapping (e.g., `{Alert: '#lib/Alert.svelte'}`).
|
|
42
42
|
* If content references a component not in this map, `has_unconfigured_tags` is set.
|
|
43
43
|
*/
|
|
44
44
|
components: Record<string, string>;
|
|
@@ -85,10 +85,10 @@ export interface MdzToSvelteOptions {
|
|
|
85
85
|
*/
|
|
86
86
|
export const mdz_to_svelte = (
|
|
87
87
|
nodes: Array<MdzNode>,
|
|
88
|
-
options: MdzToSvelteOptions
|
|
88
|
+
options: MdzToSvelteOptions
|
|
89
89
|
): MdzToSvelteResult => {
|
|
90
|
-
const {components, elements, base, code_component_import, codeblock_component_import} = options;
|
|
91
|
-
const imports: Map<string, {path: string; kind: 'default' | 'named'}> = new Map();
|
|
90
|
+
const { components, elements, base, code_component_import, codeblock_component_import } = options;
|
|
91
|
+
const imports: Map<string, { path: string; kind: 'default' | 'named' }> = new Map();
|
|
92
92
|
let has_unconfigured_tags = false;
|
|
93
93
|
|
|
94
94
|
const render_nodes = (children: Array<MdzNode>): string => {
|
|
@@ -105,7 +105,7 @@ export const mdz_to_svelte = (
|
|
|
105
105
|
return `<code>${escape_svelte_text(node.content)}</code>`;
|
|
106
106
|
}
|
|
107
107
|
const name = component_local_name(code_component_import);
|
|
108
|
-
imports.set(name, {path: code_component_import, kind: 'default'});
|
|
108
|
+
imports.set(name, { path: code_component_import, kind: 'default' });
|
|
109
109
|
return `<${name} reference={'${escape_js_string(node.content)}'} />`;
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -116,7 +116,7 @@ export const mdz_to_svelte = (
|
|
|
116
116
|
return `<pre><code>${escape_svelte_text(node.content)}</code></pre>`;
|
|
117
117
|
}
|
|
118
118
|
const name = component_local_name(codeblock_component_import);
|
|
119
|
-
imports.set(name, {path: codeblock_component_import, kind: 'default'});
|
|
119
|
+
imports.set(name, { path: codeblock_component_import, kind: 'default' });
|
|
120
120
|
return `<${name} ${lang_attr} content={'${escape_js_string(node.content)}'} />`;
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -131,26 +131,21 @@ export const mdz_to_svelte = (
|
|
|
131
131
|
|
|
132
132
|
case 'Link': {
|
|
133
133
|
const children_markup = render_nodes(node.children);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
) {
|
|
147
|
-
return `<a href={'${escape_js_string(node.reference)}'}>${children_markup}</a>`;
|
|
148
|
-
}
|
|
149
|
-
imports.set('resolve', {path: '$app/paths', kind: 'named'});
|
|
150
|
-
return `<a href={resolve('${escape_js_string(node.reference)}')}>${children_markup}</a>`;
|
|
134
|
+
// safety gate + resolve-vs-raw classification shared with the runtime
|
|
135
|
+
// views via `mdz_classify_link`, so the three renderers can't drift
|
|
136
|
+
// (an unsafe `javascript:`/`data:` ref renders as plain children, no
|
|
137
|
+
// `<a>` — defense in depth, since `mdz_to_svelte` is a public entry
|
|
138
|
+
// callable with hand-built trees that never went through `mdz_parse`)
|
|
139
|
+
const link = mdz_classify_link(node.reference, node.link_type, base);
|
|
140
|
+
if (link.kind === 'unsafe') return children_markup;
|
|
141
|
+
if (link.kind === 'resolve') {
|
|
142
|
+
imports.set('resolve', { path: '$app/paths', kind: 'named' });
|
|
143
|
+
return `<a href={resolve('${escape_js_string(link.href)}')}>${children_markup}</a>`;
|
|
144
|
+
}
|
|
145
|
+
if (link.kind === 'external') {
|
|
146
|
+
return `<a href={'${escape_js_string(link.href)}'} target="_blank" rel="noopener">${children_markup}</a>`;
|
|
151
147
|
}
|
|
152
|
-
|
|
153
|
-
return `<a href={'${escape_js_string(node.reference)}'} target="_blank" rel="noopener">${children_markup}</a>`;
|
|
148
|
+
return `<a href={'${escape_js_string(link.href)}'}>${children_markup}</a>`;
|
|
154
149
|
}
|
|
155
150
|
|
|
156
151
|
case 'Paragraph':
|
|
@@ -172,7 +167,7 @@ export const mdz_to_svelte = (
|
|
|
172
167
|
return `<blockquote>${render_nodes(node.children)}</blockquote>`;
|
|
173
168
|
|
|
174
169
|
case 'Table': {
|
|
175
|
-
const {align} = node;
|
|
170
|
+
const { align } = node;
|
|
176
171
|
const render_row = (row: MdzNodeTableRow, tag: 'th' | 'td'): string => {
|
|
177
172
|
let cells = '';
|
|
178
173
|
// normalize to the column count: pad short rows, ignore extra cells
|
|
@@ -184,19 +179,16 @@ export const mdz_to_svelte = (
|
|
|
184
179
|
}
|
|
185
180
|
return `<tr>${cells}</tr>`;
|
|
186
181
|
};
|
|
182
|
+
// by grammar the header row is always first (and the only one) —
|
|
183
|
+
// read it by index and skip it in the body, mirroring the node
|
|
184
|
+
// views (a `.filter()` pair re-partitions every row; see
|
|
185
|
+
// `MdzNodeView.svelte`'s "Table rendering de-quadratified" note)
|
|
187
186
|
let out = '<table>';
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
out += '</thead>';
|
|
194
|
-
}
|
|
195
|
-
if (body_rows.length > 0) {
|
|
196
|
-
out += '<tbody>';
|
|
197
|
-
for (const row of body_rows) out += render_row(row, 'td');
|
|
198
|
-
out += '</tbody>';
|
|
199
|
-
}
|
|
187
|
+
const header_row = node.children[0]?.header ? node.children[0] : undefined;
|
|
188
|
+
if (header_row) out += '<thead>' + render_row(header_row, 'th') + '</thead>';
|
|
189
|
+
let body = '';
|
|
190
|
+
for (const row of node.children) if (!row.header) body += render_row(row, 'td');
|
|
191
|
+
if (body) out += '<tbody>' + body + '</tbody>';
|
|
200
192
|
return out + '</table>';
|
|
201
193
|
}
|
|
202
194
|
|
|
@@ -233,7 +225,7 @@ export const mdz_to_svelte = (
|
|
|
233
225
|
has_unconfigured_tags = true;
|
|
234
226
|
return '';
|
|
235
227
|
}
|
|
236
|
-
imports.set(node.name, {path: import_path, kind: 'default'});
|
|
228
|
+
imports.set(node.name, { path: import_path, kind: 'default' });
|
|
237
229
|
return `<${node.name}>${render_nodes(node.children)}</${node.name}>`;
|
|
238
230
|
}
|
|
239
231
|
|
|
@@ -243,5 +235,5 @@ export const mdz_to_svelte = (
|
|
|
243
235
|
};
|
|
244
236
|
|
|
245
237
|
const markup = render_nodes(nodes);
|
|
246
|
-
return {markup, imports, has_unconfigured_tags};
|
|
238
|
+
return { markup, imports, has_unconfigured_tags };
|
|
247
239
|
};
|
|
@@ -22,14 +22,14 @@ import type {
|
|
|
22
22
|
MdzNodeTableRow,
|
|
23
23
|
MdzNodeTableCell,
|
|
24
24
|
MdzNodeElement,
|
|
25
|
-
MdzNodeComponent
|
|
25
|
+
MdzNodeComponent
|
|
26
26
|
} from './mdz.ts';
|
|
27
27
|
import {
|
|
28
28
|
has_non_whitespace,
|
|
29
29
|
mdz_extract_single_tag,
|
|
30
30
|
mdz_heading_id,
|
|
31
31
|
mdz_merge_adjacent_text,
|
|
32
|
-
mdz_push_merging_text
|
|
32
|
+
mdz_push_merging_text
|
|
33
33
|
} from './mdz_helpers.ts';
|
|
34
34
|
import {
|
|
35
35
|
type MdzToken,
|
|
@@ -43,9 +43,24 @@ import {
|
|
|
43
43
|
type MdzTokenListItemOpen,
|
|
44
44
|
type MdzTokenTableOpen,
|
|
45
45
|
type MdzTokenTableRowOpen,
|
|
46
|
-
type MdzTokenTableCellOpen
|
|
46
|
+
type MdzTokenTableCellOpen
|
|
47
47
|
} from './mdz_lexer.ts';
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Whether a token type ends the current inline context — the block-boundary
|
|
51
|
+
* set shared by the delimiter-pair and link children loops (an unclosed
|
|
52
|
+
* inline never swallows block structure).
|
|
53
|
+
*/
|
|
54
|
+
const token_is_block_boundary = (type: MdzToken['type']): boolean =>
|
|
55
|
+
type === 'paragraph_break' ||
|
|
56
|
+
type === 'heading_start' ||
|
|
57
|
+
type === 'hr' ||
|
|
58
|
+
type === 'codeblock' ||
|
|
59
|
+
type === 'list_open' ||
|
|
60
|
+
type === 'list_item_close' ||
|
|
61
|
+
type === 'blockquote_open' ||
|
|
62
|
+
type === 'table_open';
|
|
63
|
+
|
|
49
64
|
/**
|
|
50
65
|
* Builds an `MdzNode[]` tree from a lexed `MdzToken[]` stream.
|
|
51
66
|
* Used by `mdz_parse`, which should be preferred for simple usage.
|
|
@@ -119,7 +134,7 @@ export class MdzTokenParser {
|
|
|
119
134
|
if (token.type === 'hr') {
|
|
120
135
|
const flushed = this.#flush_paragraph(paragraph_children, true);
|
|
121
136
|
if (flushed) nodes.push(flushed);
|
|
122
|
-
nodes.push({type: 'Hr', start: token.start, end: token.end});
|
|
137
|
+
nodes.push({ type: 'Hr', start: token.start, end: token.end });
|
|
123
138
|
this.#index++;
|
|
124
139
|
continue;
|
|
125
140
|
}
|
|
@@ -132,7 +147,7 @@ export class MdzTokenParser {
|
|
|
132
147
|
lang: token.lang,
|
|
133
148
|
content: token.content,
|
|
134
149
|
start: token.start,
|
|
135
|
-
end: token.end
|
|
150
|
+
end: token.end
|
|
136
151
|
});
|
|
137
152
|
this.#index++;
|
|
138
153
|
continue;
|
|
@@ -185,7 +200,7 @@ export class MdzTokenParser {
|
|
|
185
200
|
// children-derived end, like List — no-empty-start means children exist,
|
|
186
201
|
// with the open's position as a defensive fallback
|
|
187
202
|
const end = children.length > 0 ? children[children.length - 1]!.end : open.end;
|
|
188
|
-
return {type: 'Blockquote', children, start: open.start, end};
|
|
203
|
+
return { type: 'Blockquote', children, start: open.start, end };
|
|
189
204
|
}
|
|
190
205
|
|
|
191
206
|
#parse_table(): MdzNodeTable {
|
|
@@ -208,7 +223,7 @@ export class MdzTokenParser {
|
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
const end = rows.length > 0 ? rows[rows.length - 1]!.end : open.end;
|
|
211
|
-
return {type: 'Table', align: open.align, children: rows, start: open.start, end};
|
|
226
|
+
return { type: 'Table', align: open.align, children: rows, start: open.start, end };
|
|
212
227
|
}
|
|
213
228
|
|
|
214
229
|
#parse_table_row(): MdzNodeTableRow {
|
|
@@ -226,13 +241,13 @@ export class MdzTokenParser {
|
|
|
226
241
|
}
|
|
227
242
|
if (t.type === 'table_row_close') {
|
|
228
243
|
this.#index++;
|
|
229
|
-
return {type: 'TableRow', header, children: cells, start: open.start, end: t.end};
|
|
244
|
+
return { type: 'TableRow', header, children: cells, start: open.start, end: t.end };
|
|
230
245
|
}
|
|
231
246
|
this.#index++; // skip anything unexpected (defensive)
|
|
232
247
|
}
|
|
233
248
|
|
|
234
249
|
const end = cells.length > 0 ? cells[cells.length - 1]!.end : open.end;
|
|
235
|
-
return {type: 'TableRow', header, children: cells, start: open.start, end};
|
|
250
|
+
return { type: 'TableRow', header, children: cells, start: open.start, end };
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
#parse_table_cell(): MdzNodeTableCell {
|
|
@@ -246,14 +261,14 @@ export class MdzTokenParser {
|
|
|
246
261
|
const t = this.#tokens[this.#index]!;
|
|
247
262
|
if (t.type === 'table_cell_close') {
|
|
248
263
|
this.#index++;
|
|
249
|
-
return {type: 'TableCell', children, start, end: t.end};
|
|
264
|
+
return { type: 'TableCell', children, start, end: t.end };
|
|
250
265
|
}
|
|
251
266
|
const node = this.#parse_inline();
|
|
252
267
|
if (node) mdz_push_merging_text(children, node);
|
|
253
268
|
}
|
|
254
269
|
|
|
255
270
|
const end = children.length > 0 ? children[children.length - 1]!.end : open.end;
|
|
256
|
-
return {type: 'TableCell', children, start, end};
|
|
271
|
+
return { type: 'TableCell', children, start, end };
|
|
257
272
|
}
|
|
258
273
|
|
|
259
274
|
#parse_heading(): MdzNodeHeading {
|
|
@@ -278,7 +293,7 @@ export class MdzTokenParser {
|
|
|
278
293
|
const merged = mdz_merge_adjacent_text(children);
|
|
279
294
|
const end = merged.length > 0 ? merged[merged.length - 1]!.end : start + level + 1;
|
|
280
295
|
|
|
281
|
-
return {type: 'Heading', level, id: mdz_heading_id(merged), children: merged, start, end};
|
|
296
|
+
return { type: 'Heading', level, id: mdz_heading_id(merged), children: merged, start, end };
|
|
282
297
|
}
|
|
283
298
|
|
|
284
299
|
#parse_list(): MdzNodeList {
|
|
@@ -308,10 +323,10 @@ export class MdzTokenParser {
|
|
|
308
323
|
start_number: children[0]?.number ?? 1,
|
|
309
324
|
children,
|
|
310
325
|
start: open.start,
|
|
311
|
-
end
|
|
326
|
+
end
|
|
312
327
|
};
|
|
313
328
|
}
|
|
314
|
-
return {type: 'List', ordered: false, children, start: open.start, end};
|
|
329
|
+
return { type: 'List', ordered: false, children, start: open.start, end };
|
|
315
330
|
}
|
|
316
331
|
|
|
317
332
|
#parse_list_item(): MdzNodeListItem {
|
|
@@ -361,7 +376,7 @@ export class MdzTokenParser {
|
|
|
361
376
|
lang: t.lang,
|
|
362
377
|
content: t.content,
|
|
363
378
|
start: t.start,
|
|
364
|
-
end: t.end
|
|
379
|
+
end: t.end
|
|
365
380
|
});
|
|
366
381
|
this.#index++;
|
|
367
382
|
continue;
|
|
@@ -385,9 +400,9 @@ export class MdzTokenParser {
|
|
|
385
400
|
|
|
386
401
|
const end = children.length > 0 ? children[children.length - 1]!.end : open.end;
|
|
387
402
|
if (open.number !== undefined) {
|
|
388
|
-
return {type: 'ListItem', number: open.number, children, start: open.start, end};
|
|
403
|
+
return { type: 'ListItem', number: open.number, children, start: open.start, end };
|
|
389
404
|
}
|
|
390
|
-
return {type: 'ListItem', children, start: open.start, end};
|
|
405
|
+
return { type: 'ListItem', children, start: open.start, end };
|
|
391
406
|
}
|
|
392
407
|
|
|
393
408
|
#parse_inline(): MdzNode | null {
|
|
@@ -398,11 +413,11 @@ export class MdzTokenParser {
|
|
|
398
413
|
switch (token.type) {
|
|
399
414
|
case 'text':
|
|
400
415
|
this.#index++;
|
|
401
|
-
return {type: 'Text', content: token.content, start: token.start, end: token.end};
|
|
416
|
+
return { type: 'Text', content: token.content, start: token.start, end: token.end };
|
|
402
417
|
|
|
403
418
|
case 'code':
|
|
404
419
|
this.#index++;
|
|
405
|
-
return {type: 'Code', content: token.content, start: token.start, end: token.end};
|
|
420
|
+
return { type: 'Code', content: token.content, start: token.start, end: token.end };
|
|
406
421
|
|
|
407
422
|
case 'bold_open':
|
|
408
423
|
return this.#parse_delimiter_pair('bold_close', 'Bold', '**');
|
|
@@ -428,19 +443,19 @@ export class MdzTokenParser {
|
|
|
428
443
|
// Orphaned close tokens - treat as text
|
|
429
444
|
case 'bold_close':
|
|
430
445
|
this.#index++;
|
|
431
|
-
return {type: 'Text', content: '**', start: token.start, end: token.end};
|
|
446
|
+
return { type: 'Text', content: '**', start: token.start, end: token.end };
|
|
432
447
|
|
|
433
448
|
case 'italic_close':
|
|
434
449
|
this.#index++;
|
|
435
|
-
return {type: 'Text', content: '_', start: token.start, end: token.end};
|
|
450
|
+
return { type: 'Text', content: '_', start: token.start, end: token.end };
|
|
436
451
|
|
|
437
452
|
case 'strikethrough_close':
|
|
438
453
|
this.#index++;
|
|
439
|
-
return {type: 'Text', content: '~~', start: token.start, end: token.end};
|
|
454
|
+
return { type: 'Text', content: '~~', start: token.start, end: token.end };
|
|
440
455
|
|
|
441
456
|
case 'link_text_close':
|
|
442
457
|
this.#index++;
|
|
443
|
-
return {type: 'Text', content: ']', start: token.start, end: token.end};
|
|
458
|
+
return { type: 'Text', content: ']', start: token.start, end: token.end };
|
|
444
459
|
|
|
445
460
|
case 'link_ref':
|
|
446
461
|
this.#index++;
|
|
@@ -448,7 +463,7 @@ export class MdzTokenParser {
|
|
|
448
463
|
type: 'Text',
|
|
449
464
|
content: `(${token.reference})`,
|
|
450
465
|
start: token.start,
|
|
451
|
-
end: token.end
|
|
466
|
+
end: token.end
|
|
452
467
|
};
|
|
453
468
|
|
|
454
469
|
case 'tag_close':
|
|
@@ -457,7 +472,7 @@ export class MdzTokenParser {
|
|
|
457
472
|
type: 'Text',
|
|
458
473
|
content: `</${token.name}>`,
|
|
459
474
|
start: token.start,
|
|
460
|
-
end: token.end
|
|
475
|
+
end: token.end
|
|
461
476
|
};
|
|
462
477
|
|
|
463
478
|
default:
|
|
@@ -477,7 +492,7 @@ export class MdzTokenParser {
|
|
|
477
492
|
#parse_delimiter_pair(
|
|
478
493
|
close_type: 'bold_close' | 'italic_close' | 'strikethrough_close',
|
|
479
494
|
node_type: 'Bold' | 'Italic' | 'Strikethrough',
|
|
480
|
-
delimiter: '**' | '_' | '~~'
|
|
495
|
+
delimiter: '**' | '_' | '~~'
|
|
481
496
|
): MdzNodeBold | MdzNodeItalic | MdzNodeStrikethrough | MdzNodeText {
|
|
482
497
|
const start = this.#tokens[this.#index]!.start;
|
|
483
498
|
this.#index++;
|
|
@@ -488,18 +503,9 @@ export class MdzTokenParser {
|
|
|
488
503
|
const t = this.#tokens[this.#index]!;
|
|
489
504
|
if (t.type === close_type) {
|
|
490
505
|
this.#index++;
|
|
491
|
-
return {type: node_type, children, start, end: t.end};
|
|
506
|
+
return { type: node_type, children, start, end: t.end };
|
|
492
507
|
}
|
|
493
|
-
if (
|
|
494
|
-
t.type === 'paragraph_break' ||
|
|
495
|
-
t.type === 'heading_start' ||
|
|
496
|
-
t.type === 'hr' ||
|
|
497
|
-
t.type === 'codeblock' ||
|
|
498
|
-
t.type === 'list_open' ||
|
|
499
|
-
t.type === 'list_item_close' ||
|
|
500
|
-
t.type === 'blockquote_open' ||
|
|
501
|
-
t.type === 'table_open'
|
|
502
|
-
) {
|
|
508
|
+
if (token_is_block_boundary(t.type)) {
|
|
503
509
|
break;
|
|
504
510
|
}
|
|
505
511
|
const node = this.#parse_inline();
|
|
@@ -507,7 +513,7 @@ export class MdzTokenParser {
|
|
|
507
513
|
}
|
|
508
514
|
|
|
509
515
|
// Unclosed - treat as text
|
|
510
|
-
return {type: 'Text', content: delimiter, start, end: start + delimiter.length};
|
|
516
|
+
return { type: 'Text', content: delimiter, start, end: start + delimiter.length };
|
|
511
517
|
}
|
|
512
518
|
|
|
513
519
|
#parse_link(): MdzNodeLink | MdzNodeText {
|
|
@@ -532,30 +538,21 @@ export class MdzTokenParser {
|
|
|
532
538
|
children,
|
|
533
539
|
link_type: ref_token.link_type,
|
|
534
540
|
start,
|
|
535
|
-
end: ref_token.end
|
|
541
|
+
end: ref_token.end
|
|
536
542
|
};
|
|
537
543
|
}
|
|
538
544
|
|
|
539
545
|
// No link_ref - treat as text
|
|
540
|
-
return {type: 'Text', content: '[', start, end: start + 1};
|
|
546
|
+
return { type: 'Text', content: '[', start, end: start + 1 };
|
|
541
547
|
}
|
|
542
|
-
if (
|
|
543
|
-
t.type === 'paragraph_break' ||
|
|
544
|
-
t.type === 'heading_start' ||
|
|
545
|
-
t.type === 'hr' ||
|
|
546
|
-
t.type === 'codeblock' ||
|
|
547
|
-
t.type === 'list_open' ||
|
|
548
|
-
t.type === 'list_item_close' ||
|
|
549
|
-
t.type === 'blockquote_open' ||
|
|
550
|
-
t.type === 'table_open'
|
|
551
|
-
) {
|
|
548
|
+
if (token_is_block_boundary(t.type)) {
|
|
552
549
|
break;
|
|
553
550
|
}
|
|
554
551
|
const node = this.#parse_inline();
|
|
555
552
|
if (node) mdz_push_merging_text(children, node);
|
|
556
553
|
}
|
|
557
554
|
|
|
558
|
-
return {type: 'Text', content: '[', start, end: start + 1};
|
|
555
|
+
return { type: 'Text', content: '[', start, end: start + 1 };
|
|
559
556
|
}
|
|
560
557
|
|
|
561
558
|
#parse_autolink(): MdzNodeLink {
|
|
@@ -564,10 +561,10 @@ export class MdzTokenParser {
|
|
|
564
561
|
return {
|
|
565
562
|
type: 'Link',
|
|
566
563
|
reference: token.reference,
|
|
567
|
-
children: [{type: 'Text', content: token.reference, start: token.start, end: token.end}],
|
|
564
|
+
children: [{ type: 'Text', content: token.reference, start: token.start, end: token.end }],
|
|
568
565
|
link_type: token.link_type,
|
|
569
566
|
start: token.start,
|
|
570
|
-
end: token.end
|
|
567
|
+
end: token.end
|
|
571
568
|
};
|
|
572
569
|
}
|
|
573
570
|
|
|
@@ -584,21 +581,21 @@ export class MdzTokenParser {
|
|
|
584
581
|
const t = this.#tokens[this.#index]!;
|
|
585
582
|
if (t.type === 'tag_close' && t.name === tag_name) {
|
|
586
583
|
this.#index++;
|
|
587
|
-
return {type: node_type, name: tag_name, children, start, end: t.end};
|
|
584
|
+
return { type: node_type, name: tag_name, children, start, end: t.end };
|
|
588
585
|
}
|
|
589
586
|
const node = this.#parse_inline();
|
|
590
587
|
if (node) mdz_push_merging_text(children, node);
|
|
591
588
|
}
|
|
592
589
|
|
|
593
590
|
// Unclosed tag
|
|
594
|
-
return {type: 'Text', content: '<', start, end: start + 1};
|
|
591
|
+
return { type: 'Text', content: '<', start, end: start + 1 };
|
|
595
592
|
}
|
|
596
593
|
|
|
597
594
|
#parse_self_close_tag(): MdzNodeElement | MdzNodeComponent {
|
|
598
595
|
const token = this.#tokens[this.#index]! as MdzTokenTagSelfClose;
|
|
599
596
|
const node_type: 'Component' | 'Element' = token.is_component ? 'Component' : 'Element';
|
|
600
597
|
this.#index++;
|
|
601
|
-
return {type: node_type, name: token.name, children: [], start: token.start, end: token.end};
|
|
598
|
+
return { type: node_type, name: token.name, children: [], start: token.start, end: token.end };
|
|
602
599
|
}
|
|
603
600
|
|
|
604
601
|
// -- Paragraph flushing --
|
|
@@ -630,7 +627,7 @@ export class MdzTokenParser {
|
|
|
630
627
|
// `has_non_whitespace_content` (Unicode whitespace like NBSP is
|
|
631
628
|
// content; `.trim()` here would silently drop it)
|
|
632
629
|
const has_content = paragraph_children.some(
|
|
633
|
-
(n) => n.type !== 'Text' || has_non_whitespace(n.content)
|
|
630
|
+
(n) => n.type !== 'Text' || has_non_whitespace(n.content)
|
|
634
631
|
);
|
|
635
632
|
if (!has_content) {
|
|
636
633
|
paragraph_children.length = 0;
|
|
@@ -655,7 +652,7 @@ export class MdzTokenParser {
|
|
|
655
652
|
type: 'Paragraph',
|
|
656
653
|
children: merged,
|
|
657
654
|
start: merged[0]!.start,
|
|
658
|
-
end: merged[merged.length - 1]!.end
|
|
655
|
+
end: merged[merged.length - 1]!.end
|
|
659
656
|
};
|
|
660
657
|
}
|
|
661
658
|
}
|