@fuzdev/fuz_ui 0.203.1 → 0.205.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/dist/Alert.svelte +31 -17
- package/dist/Alert.svelte.d.ts +8 -2
- package/dist/Alert.svelte.d.ts.map +1 -1
- package/dist/ApiModule.svelte +14 -1
- package/dist/ApiModule.svelte.d.ts.map +1 -1
- package/dist/Card.svelte +28 -10
- package/dist/Card.svelte.d.ts +7 -2
- package/dist/Card.svelte.d.ts.map +1 -1
- package/dist/CopyToClipboard.svelte +5 -1
- package/dist/CopyToClipboard.svelte.d.ts.map +1 -1
- package/dist/DeclarationDetail.svelte +15 -4
- package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
- package/dist/DocsLink.svelte +15 -4
- package/dist/DocsLink.svelte.d.ts +8 -1
- package/dist/DocsLink.svelte.d.ts.map +1 -1
- package/dist/DocsMenu.svelte +1 -1
- package/dist/DocsPageLinks.svelte +1 -1
- package/dist/EcosystemLinks.svelte +5 -2
- package/dist/EcosystemLinks.svelte.d.ts +2 -1
- package/dist/EcosystemLinks.svelte.d.ts.map +1 -1
- package/dist/EcosystemLinksPanel.svelte +4 -2
- package/dist/EcosystemLinksPanel.svelte.d.ts +2 -1
- package/dist/EcosystemLinksPanel.svelte.d.ts.map +1 -1
- package/dist/Hashlink.svelte +1 -1
- package/dist/ImgOrSvg.svelte +6 -5
- package/dist/ImgOrSvg.svelte.d.ts +2 -1
- package/dist/ImgOrSvg.svelte.d.ts.map +1 -1
- package/dist/LibraryDetail.svelte +5 -2
- package/dist/LibraryDetail.svelte.d.ts +3 -1
- package/dist/LibraryDetail.svelte.d.ts.map +1 -1
- package/dist/LibrarySummary.svelte +4 -2
- package/dist/LibrarySummary.svelte.d.ts +2 -1
- package/dist/LibrarySummary.svelte.d.ts.map +1 -1
- package/dist/PasteFromClipboard.svelte +3 -1
- package/dist/PasteFromClipboard.svelte.d.ts.map +1 -1
- package/dist/PendingButton.svelte +0 -2
- package/dist/PendingButton.svelte.d.ts.map +1 -1
- package/dist/StyleVariableButton.svelte +5 -1
- package/dist/StyleVariableButton.svelte.d.ts.map +1 -1
- package/dist/Svg.svelte +1 -1
- package/dist/TomeContent.svelte +5 -2
- package/dist/TomeContent.svelte.d.ts +2 -1
- package/dist/TomeContent.svelte.d.ts.map +1 -1
- package/dist/TypeLink.svelte +13 -4
- package/dist/TypeLink.svelte.d.ts +7 -1
- package/dist/TypeLink.svelte.d.ts.map +1 -1
- package/package.json +12 -36
- package/dist/Mdz.svelte +0 -34
- package/dist/Mdz.svelte.d.ts +0 -11
- package/dist/Mdz.svelte.d.ts.map +0 -1
- package/dist/MdzNodeView.svelte +0 -107
- package/dist/MdzNodeView.svelte.d.ts +0 -9
- package/dist/MdzNodeView.svelte.d.ts.map +0 -1
- package/dist/MdzPrecompiled.svelte +0 -30
- package/dist/MdzPrecompiled.svelte.d.ts +0 -11
- package/dist/MdzPrecompiled.svelte.d.ts.map +0 -1
- package/dist/MdzRoot.svelte +0 -30
- package/dist/MdzRoot.svelte.d.ts +0 -12
- package/dist/MdzRoot.svelte.d.ts.map +0 -1
- package/dist/MdzStream.svelte +0 -32
- package/dist/MdzStream.svelte.d.ts +0 -12
- package/dist/MdzStream.svelte.d.ts.map +0 -1
- package/dist/MdzStreamNodeView.svelte +0 -106
- package/dist/MdzStreamNodeView.svelte.d.ts +0 -9
- package/dist/MdzStreamNodeView.svelte.d.ts.map +0 -1
- package/dist/mdz.d.ts +0 -112
- package/dist/mdz.d.ts.map +0 -1
- package/dist/mdz.js +0 -1186
- package/dist/mdz_components.d.ts +0 -63
- package/dist/mdz_components.d.ts.map +0 -1
- package/dist/mdz_components.js +0 -32
- package/dist/mdz_helpers.d.ts +0 -164
- package/dist/mdz_helpers.d.ts.map +0 -1
- package/dist/mdz_helpers.js +0 -424
- package/dist/mdz_lexer.d.ts +0 -93
- package/dist/mdz_lexer.d.ts.map +0 -1
- package/dist/mdz_lexer.js +0 -732
- package/dist/mdz_opcodes.d.ts +0 -174
- package/dist/mdz_opcodes.d.ts.map +0 -1
- package/dist/mdz_opcodes.js +0 -14
- package/dist/mdz_opcodes_to_nodes.d.ts +0 -20
- package/dist/mdz_opcodes_to_nodes.d.ts.map +0 -1
- package/dist/mdz_opcodes_to_nodes.js +0 -332
- package/dist/mdz_stream_parser.d.ts +0 -80
- package/dist/mdz_stream_parser.d.ts.map +0 -1
- package/dist/mdz_stream_parser.js +0 -354
- package/dist/mdz_stream_parser_block.d.ts +0 -76
- package/dist/mdz_stream_parser_block.d.ts.map +0 -1
- package/dist/mdz_stream_parser_block.js +0 -458
- package/dist/mdz_stream_parser_inline.d.ts +0 -52
- package/dist/mdz_stream_parser_inline.d.ts.map +0 -1
- package/dist/mdz_stream_parser_inline.js +0 -378
- package/dist/mdz_stream_parser_link.d.ts +0 -22
- package/dist/mdz_stream_parser_link.d.ts.map +0 -1
- package/dist/mdz_stream_parser_link.js +0 -215
- package/dist/mdz_stream_parser_state.d.ts +0 -187
- package/dist/mdz_stream_parser_state.d.ts.map +0 -1
- package/dist/mdz_stream_parser_state.js +0 -398
- package/dist/mdz_stream_parser_text.d.ts +0 -14
- package/dist/mdz_stream_parser_text.d.ts.map +0 -1
- package/dist/mdz_stream_parser_text.js +0 -50
- package/dist/mdz_stream_parser_url.d.ts +0 -60
- package/dist/mdz_stream_parser_url.d.ts.map +0 -1
- package/dist/mdz_stream_parser_url.js +0 -307
- package/dist/mdz_stream_state.svelte.d.ts +0 -44
- package/dist/mdz_stream_state.svelte.d.ts.map +0 -1
- package/dist/mdz_stream_state.svelte.js +0 -357
- package/dist/mdz_to_svelte.d.ts +0 -41
- package/dist/mdz_to_svelte.d.ts.map +0 -1
- package/dist/mdz_to_svelte.js +0 -100
- package/dist/mdz_token_parser.d.ts +0 -14
- package/dist/mdz_token_parser.d.ts.map +0 -1
- package/dist/mdz_token_parser.js +0 -344
- package/dist/svelte_preprocess_mdz.d.ts +0 -65
- package/dist/svelte_preprocess_mdz.d.ts.map +0 -1
- package/dist/svelte_preprocess_mdz.js +0 -529
- package/dist/tsdoc_mdz.d.ts +0 -45
- package/dist/tsdoc_mdz.d.ts.map +0 -1
- package/dist/tsdoc_mdz.js +0 -88
- package/src/lib/mdz.ts +0 -1532
- package/src/lib/mdz_components.ts +0 -64
- package/src/lib/mdz_helpers.ts +0 -449
- package/src/lib/mdz_lexer.ts +0 -1012
- package/src/lib/mdz_opcodes.ts +0 -205
- package/src/lib/mdz_opcodes_to_nodes.ts +0 -375
- package/src/lib/mdz_stream_parser.ts +0 -415
- package/src/lib/mdz_stream_parser_block.ts +0 -532
- package/src/lib/mdz_stream_parser_inline.ts +0 -414
- package/src/lib/mdz_stream_parser_link.ts +0 -271
- package/src/lib/mdz_stream_parser_state.ts +0 -539
- package/src/lib/mdz_stream_parser_text.ts +0 -77
- package/src/lib/mdz_stream_parser_url.ts +0 -365
- package/src/lib/mdz_stream_state.svelte.ts +0 -387
- package/src/lib/mdz_to_svelte.ts +0 -141
- package/src/lib/mdz_token_parser.ts +0 -434
- package/src/lib/svelte_preprocess_mdz.ts +0 -742
- package/src/lib/tsdoc_mdz.ts +0 -97
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inline-formatting handlers (bold, italic, strikethrough, inline code)
|
|
3
|
-
* for the streaming mdz parser.
|
|
4
|
-
*
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
|
-
import { ASTERISK, BACKTICK, NEWLINE, TILDE, UNDERSCORE, is_word_char } from './mdz_helpers.js';
|
|
8
|
-
import { accumulate_text, alloc_id, emit, ensure_paragraph, flush_text, offset, push_stack_entry, revert_above, } from './mdz_stream_parser_state.js';
|
|
9
|
-
// -- Bold --
|
|
10
|
-
export const try_bold = (state) => {
|
|
11
|
-
// we know we're at ** and there's no open Bold
|
|
12
|
-
if (state.pos + 1 >= state.buffer.length)
|
|
13
|
-
return false; // need more input
|
|
14
|
-
// need at least one char after ** to avoid empty bold
|
|
15
|
-
if (state.pos + 2 >= state.buffer.length)
|
|
16
|
-
return false;
|
|
17
|
-
flush_text(state);
|
|
18
|
-
ensure_paragraph(state);
|
|
19
|
-
const id = alloc_id(state);
|
|
20
|
-
const bold_start = offset(state);
|
|
21
|
-
emit(state, { type: 'open', id, node_type: 'Bold', start: bold_start });
|
|
22
|
-
push_stack_entry(state, id, 'Bold', bold_start, true, '**');
|
|
23
|
-
state.active_text_id = null;
|
|
24
|
-
state.pos += 2;
|
|
25
|
-
state.column += 2;
|
|
26
|
-
state.prev_char = ASTERISK;
|
|
27
|
-
return true;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Close a `Bold` container at the given stack index. The caller in
|
|
31
|
-
* `process_inline` already located the open via `find_open`, so we trust the
|
|
32
|
-
* index rather than re-walking the stack.
|
|
33
|
-
*/
|
|
34
|
-
export const close_bold = (state, stack_idx) => {
|
|
35
|
-
flush_text(state);
|
|
36
|
-
// revert anything between the bold and the top of the stack
|
|
37
|
-
revert_above(state, stack_idx);
|
|
38
|
-
const entry = state.stack.pop();
|
|
39
|
-
// check for empty container — revert to literal text instead of closing
|
|
40
|
-
if (!entry.has_children) {
|
|
41
|
-
emit(state, {
|
|
42
|
-
type: 'revert',
|
|
43
|
-
id: entry.id,
|
|
44
|
-
replacement_text: entry.delimiter,
|
|
45
|
-
start: entry.start,
|
|
46
|
-
});
|
|
47
|
-
accumulate_text(state, '**', offset(state));
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
emit(state, { type: 'close', id: entry.id, end: offset(state) + 2 });
|
|
51
|
-
}
|
|
52
|
-
state.active_text_id = null;
|
|
53
|
-
state.pos += 2;
|
|
54
|
-
state.column += 2;
|
|
55
|
-
state.prev_char = ASTERISK;
|
|
56
|
-
return true;
|
|
57
|
-
};
|
|
58
|
-
// -- Italic --
|
|
59
|
-
export const try_italic = (state, forced = false) => {
|
|
60
|
-
// check opening word boundary
|
|
61
|
-
if (state.prev_char !== -1 && is_word_char(state.prev_char)) {
|
|
62
|
-
// not at word boundary — emit as text
|
|
63
|
-
ensure_paragraph(state);
|
|
64
|
-
accumulate_text(state, '_', offset(state));
|
|
65
|
-
state.prev_char = UNDERSCORE;
|
|
66
|
-
state.column++;
|
|
67
|
-
state.pos++;
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
// need at least one char after _
|
|
71
|
-
if (state.pos + 1 >= state.buffer.length) {
|
|
72
|
-
if (forced) {
|
|
73
|
-
// at EOF with only _ left — treat as text
|
|
74
|
-
ensure_paragraph(state);
|
|
75
|
-
accumulate_text(state, '_', offset(state));
|
|
76
|
-
state.prev_char = UNDERSCORE;
|
|
77
|
-
state.column++;
|
|
78
|
-
state.pos++;
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
// Italic is non-optimistic: require confirmed closer in the buffer.
|
|
84
|
-
// This avoids flashing italic on common `_` usage (snake_case, identifiers).
|
|
85
|
-
// When the closer isn't visible yet (`pending`), we treat `_` as text rather
|
|
86
|
-
// than holding — holding would block all subsequent content from being
|
|
87
|
-
// parsed in normal mode, and at EOF the parser can't distinguish "still
|
|
88
|
-
// streaming" from "really done."
|
|
89
|
-
//
|
|
90
|
-
// Consequence: italic cannot span chunk boundaries when the closing `_`
|
|
91
|
-
// arrives in a later chunk. The one-shot path (the correctness gate) is
|
|
92
|
-
// always correct because the full buffer is visible. See the
|
|
93
|
-
// `deliberate-divergence` block in `mdz_parser_parity.test.ts` for an
|
|
94
|
-
// asserted gap.
|
|
95
|
-
const closer = scan_closer_boundary(state, '_');
|
|
96
|
-
if (closer !== 'confirmed') {
|
|
97
|
-
ensure_paragraph(state);
|
|
98
|
-
accumulate_text(state, '_', offset(state));
|
|
99
|
-
state.prev_char = UNDERSCORE;
|
|
100
|
-
state.column++;
|
|
101
|
-
state.pos++;
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
flush_text(state);
|
|
105
|
-
ensure_paragraph(state);
|
|
106
|
-
const id = alloc_id(state);
|
|
107
|
-
const italic_start = offset(state);
|
|
108
|
-
emit(state, { type: 'open', id, node_type: 'Italic', start: italic_start });
|
|
109
|
-
push_stack_entry(state, id, 'Italic', italic_start, true, '_');
|
|
110
|
-
state.active_text_id = null;
|
|
111
|
-
state.pos++;
|
|
112
|
-
state.column++;
|
|
113
|
-
state.prev_char = UNDERSCORE;
|
|
114
|
-
return true;
|
|
115
|
-
};
|
|
116
|
-
// -- Strikethrough --
|
|
117
|
-
export const try_strikethrough = (state) => {
|
|
118
|
-
// check opening word boundary
|
|
119
|
-
if (state.prev_char !== -1 && is_word_char(state.prev_char)) {
|
|
120
|
-
ensure_paragraph(state);
|
|
121
|
-
accumulate_text(state, '~', offset(state));
|
|
122
|
-
state.prev_char = TILDE;
|
|
123
|
-
state.column++;
|
|
124
|
-
state.pos++;
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
if (state.pos + 1 >= state.buffer.length)
|
|
128
|
-
return false;
|
|
129
|
-
// strikethrough is optimistic: 'pending' proceeds, only 'rejected' aborts
|
|
130
|
-
if (scan_closer_boundary(state, '~') === 'rejected') {
|
|
131
|
-
ensure_paragraph(state);
|
|
132
|
-
accumulate_text(state, '~', offset(state));
|
|
133
|
-
state.prev_char = TILDE;
|
|
134
|
-
state.column++;
|
|
135
|
-
state.pos++;
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
flush_text(state);
|
|
139
|
-
ensure_paragraph(state);
|
|
140
|
-
const id = alloc_id(state);
|
|
141
|
-
const strike_start = offset(state);
|
|
142
|
-
emit(state, { type: 'open', id, node_type: 'Strikethrough', start: strike_start });
|
|
143
|
-
push_stack_entry(state, id, 'Strikethrough', strike_start, true, '~');
|
|
144
|
-
state.active_text_id = null;
|
|
145
|
-
state.pos++;
|
|
146
|
-
state.column++;
|
|
147
|
-
state.prev_char = TILDE;
|
|
148
|
-
return true;
|
|
149
|
-
};
|
|
150
|
-
// -- Single delimiter close (italic/strikethrough) --
|
|
151
|
-
export const check_close_word_boundary = (state) => {
|
|
152
|
-
const next_pos = state.pos + 1;
|
|
153
|
-
if (next_pos < state.buffer.length) {
|
|
154
|
-
return !is_word_char(state.buffer.charCodeAt(next_pos));
|
|
155
|
-
}
|
|
156
|
-
// at end of buffer — we'll treat as boundary for now (finish() handles unclosed)
|
|
157
|
-
return true;
|
|
158
|
-
};
|
|
159
|
-
export const close_single_delimiter = (state, stack_idx) => {
|
|
160
|
-
flush_text(state);
|
|
161
|
-
revert_above(state, stack_idx);
|
|
162
|
-
const entry = state.stack.pop();
|
|
163
|
-
// check for empty container — revert to literal text instead of closing
|
|
164
|
-
if (!entry.has_children) {
|
|
165
|
-
emit(state, {
|
|
166
|
-
type: 'revert',
|
|
167
|
-
id: entry.id,
|
|
168
|
-
replacement_text: entry.delimiter,
|
|
169
|
-
start: entry.start,
|
|
170
|
-
});
|
|
171
|
-
accumulate_text(state, entry.delimiter, offset(state));
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
emit(state, { type: 'close', id: entry.id, end: offset(state) + 1 });
|
|
175
|
-
}
|
|
176
|
-
state.active_text_id = null;
|
|
177
|
-
state.pos++;
|
|
178
|
-
state.column++;
|
|
179
|
-
state.prev_char = state.buffer.charCodeAt(state.pos - 1);
|
|
180
|
-
return true;
|
|
181
|
-
};
|
|
182
|
-
/**
|
|
183
|
-
* Scan for the first potential closing delimiter and check its word boundary.
|
|
184
|
-
* Returns a tri-state:
|
|
185
|
-
* - `'confirmed'`: closer found in buffer with valid word boundary
|
|
186
|
-
* - `'rejected'`: closer found but with invalid word boundary (greedy first-match fails)
|
|
187
|
-
* - `'pending'`: no closer in buffer yet, or can't determine boundary
|
|
188
|
-
*
|
|
189
|
-
* Optimistic constructs (strikethrough) treat `'pending'` as proceed.
|
|
190
|
-
* Non-optimistic constructs (italic) treat `'pending'` as text.
|
|
191
|
-
*
|
|
192
|
-
* Uses native `indexOf` instead of a manual char loop for faster scanning.
|
|
193
|
-
*/
|
|
194
|
-
export const scan_closer_boundary = (state, delimiter) => {
|
|
195
|
-
const search_start = state.pos + 1;
|
|
196
|
-
const delim_pos = state.buffer.indexOf(delimiter, search_start);
|
|
197
|
-
if (delim_pos === -1)
|
|
198
|
-
return 'pending'; // no closer in buffer
|
|
199
|
-
// check for paragraph break before the delimiter
|
|
200
|
-
const para_pos = state.buffer.indexOf('\n\n', search_start);
|
|
201
|
-
if (para_pos !== -1 && para_pos < delim_pos)
|
|
202
|
-
return 'pending'; // paragraph break first
|
|
203
|
-
// check word boundary after the closer
|
|
204
|
-
const after_pos = delim_pos + 1;
|
|
205
|
-
if (after_pos < state.buffer.length) {
|
|
206
|
-
return is_word_char(state.buffer.charCodeAt(after_pos)) ? 'rejected' : 'confirmed';
|
|
207
|
-
}
|
|
208
|
-
// closer at end of buffer — treat as confirmed, consistent with
|
|
209
|
-
// check_close_word_boundary which also assumes boundary at buffer end
|
|
210
|
-
return 'confirmed';
|
|
211
|
-
};
|
|
212
|
-
// -- Inline code --
|
|
213
|
-
export const try_code = (state) => {
|
|
214
|
-
const start = state.pos;
|
|
215
|
-
let i = start + 1; // past opening backtick
|
|
216
|
-
// compute search boundary: don't scan past open formatting delimiters
|
|
217
|
-
const search_limit = code_search_limit(state, start + 1);
|
|
218
|
-
// scan for closing backtick (must be before newline and search boundary)
|
|
219
|
-
while (i < state.buffer.length && i < search_limit) {
|
|
220
|
-
const c = state.buffer.charCodeAt(i);
|
|
221
|
-
if (c === BACKTICK) {
|
|
222
|
-
// found close
|
|
223
|
-
const content = state.buffer.slice(start + 1, i);
|
|
224
|
-
if (content.length === 0) {
|
|
225
|
-
// empty code — treat as text
|
|
226
|
-
ensure_paragraph(state);
|
|
227
|
-
accumulate_text(state, '``', offset(state, start));
|
|
228
|
-
state.pos = i + 1;
|
|
229
|
-
state.column += 2;
|
|
230
|
-
state.prev_char = BACKTICK;
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
flush_text(state);
|
|
234
|
-
ensure_paragraph(state);
|
|
235
|
-
const id = alloc_id(state);
|
|
236
|
-
emit(state, {
|
|
237
|
-
type: 'text',
|
|
238
|
-
id,
|
|
239
|
-
content,
|
|
240
|
-
text_type: 'Code',
|
|
241
|
-
start: offset(state, start),
|
|
242
|
-
end: offset(state, i + 1),
|
|
243
|
-
});
|
|
244
|
-
state.active_text_id = null;
|
|
245
|
-
state.pos = i + 1;
|
|
246
|
-
state.column += i + 1 - start;
|
|
247
|
-
state.prev_char = BACKTICK;
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
if (c === NEWLINE) {
|
|
251
|
-
// inline code can't span lines — treat opening backtick as text
|
|
252
|
-
ensure_paragraph(state);
|
|
253
|
-
accumulate_text(state, '`', offset(state, start));
|
|
254
|
-
state.pos = start + 1;
|
|
255
|
-
state.column++;
|
|
256
|
-
state.prev_char = BACKTICK;
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
i++;
|
|
260
|
-
}
|
|
261
|
-
// hit search limit (open formatting delimiter boundary) — treat backtick as text
|
|
262
|
-
if (i < state.buffer.length && i >= search_limit) {
|
|
263
|
-
ensure_paragraph(state);
|
|
264
|
-
accumulate_text(state, '`', offset(state, start));
|
|
265
|
-
state.pos = start + 1;
|
|
266
|
-
state.column++;
|
|
267
|
-
state.prev_char = BACKTICK;
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
// buffer ended without close or newline.
|
|
271
|
-
// If formatting is on the stack, hold — the formatting delimiter might appear
|
|
272
|
-
// in the next chunk between the backtick and its closer, and going optimistic
|
|
273
|
-
// would make the code span consume the formatting closer as raw text.
|
|
274
|
-
if (has_inline_formatting_on_stack(state)) {
|
|
275
|
-
return false; // hold for more input
|
|
276
|
-
}
|
|
277
|
-
// no formatting on stack — open Code optimistically.
|
|
278
|
-
// Content streams via text/append_text inside the container.
|
|
279
|
-
// Closes on matching backtick, reverts on newline.
|
|
280
|
-
flush_text(state);
|
|
281
|
-
ensure_paragraph(state);
|
|
282
|
-
const id = alloc_id(state);
|
|
283
|
-
const code_start = offset(state, start);
|
|
284
|
-
emit(state, { type: 'open', id, node_type: 'Code', start: code_start });
|
|
285
|
-
push_stack_entry(state, id, 'Code', code_start, true, '`');
|
|
286
|
-
state.in_code = true;
|
|
287
|
-
state.active_text_id = null;
|
|
288
|
-
state.pos = start + 1; // past opening backtick
|
|
289
|
-
state.column++;
|
|
290
|
-
state.prev_char = BACKTICK;
|
|
291
|
-
return true;
|
|
292
|
-
};
|
|
293
|
-
/**
|
|
294
|
-
* Compute search limit for inline code scanning.
|
|
295
|
-
* Finds the closest closing delimiter for any open formatting on the stack.
|
|
296
|
-
* This prevents backtick scanning from crossing bold/italic/etc boundaries.
|
|
297
|
-
*/
|
|
298
|
-
export const code_search_limit = (state, from) => {
|
|
299
|
-
let limit = state.buffer.length;
|
|
300
|
-
for (let s = state.stack.length - 1; s >= 0; s--) {
|
|
301
|
-
const entry = state.stack[s];
|
|
302
|
-
if (entry.node_type === 'Paragraph' || entry.node_type === 'Heading')
|
|
303
|
-
break;
|
|
304
|
-
// find the closing delimiter in the buffer
|
|
305
|
-
const delimiter = entry.delimiter;
|
|
306
|
-
if (!delimiter)
|
|
307
|
-
continue;
|
|
308
|
-
const idx = state.buffer.indexOf(delimiter, from);
|
|
309
|
-
if (idx !== -1 && idx < limit) {
|
|
310
|
-
limit = idx;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return limit;
|
|
314
|
-
};
|
|
315
|
-
/**
|
|
316
|
-
* Check if any inline formatting container (Bold, Italic, Strikethrough, Link)
|
|
317
|
-
* is on the stack above the current block boundary.
|
|
318
|
-
* Used to decide whether backtick can go optimistic — when formatting is open,
|
|
319
|
-
* the code span might cross the formatting boundary, so we hold instead.
|
|
320
|
-
*/
|
|
321
|
-
export const has_inline_formatting_on_stack = (state) => {
|
|
322
|
-
for (let s = state.stack.length - 1; s >= 0; s--) {
|
|
323
|
-
const entry = state.stack[s];
|
|
324
|
-
if (entry.node_type === 'Paragraph' || entry.node_type === 'Heading')
|
|
325
|
-
return false;
|
|
326
|
-
if (entry.delimiter)
|
|
327
|
-
return true;
|
|
328
|
-
}
|
|
329
|
-
return false;
|
|
330
|
-
};
|
|
331
|
-
/**
|
|
332
|
-
* Process content inside an optimistic inline Code container.
|
|
333
|
-
* Scans raw text (no inline formatting) until closing backtick or newline.
|
|
334
|
-
* Backtick closes (or reverts if empty). Newline reverts (code can't span lines).
|
|
335
|
-
*/
|
|
336
|
-
export const process_code_content = (state) => {
|
|
337
|
-
const char_code = state.buffer.charCodeAt(state.pos);
|
|
338
|
-
if (char_code === BACKTICK) {
|
|
339
|
-
flush_text(state);
|
|
340
|
-
const entry = state.stack.pop();
|
|
341
|
-
if (!entry.has_children) {
|
|
342
|
-
// empty code `` — revert opener, accumulate closer as text
|
|
343
|
-
emit(state, { type: 'revert', id: entry.id, replacement_text: '`', start: entry.start });
|
|
344
|
-
accumulate_text(state, '`', offset(state));
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
emit(state, { type: 'close', id: entry.id, end: offset(state) + 1 });
|
|
348
|
-
}
|
|
349
|
-
state.in_code = false;
|
|
350
|
-
state.active_text_id = null;
|
|
351
|
-
state.pos++;
|
|
352
|
-
state.column++;
|
|
353
|
-
state.prev_char = BACKTICK;
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (char_code === NEWLINE) {
|
|
357
|
-
// code spans can't cross lines — revert
|
|
358
|
-
flush_text(state);
|
|
359
|
-
const entry = state.stack.pop();
|
|
360
|
-
emit(state, { type: 'revert', id: entry.id, replacement_text: '`', start: entry.start });
|
|
361
|
-
state.in_code = false;
|
|
362
|
-
state.active_text_id = null;
|
|
363
|
-
// don't consume newline — outer loop handles it
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
// scan raw text until backtick or newline (paragraph already ensured at code open)
|
|
367
|
-
const start = state.pos;
|
|
368
|
-
state.pos++;
|
|
369
|
-
while (state.pos < state.buffer.length) {
|
|
370
|
-
const c = state.buffer.charCodeAt(state.pos);
|
|
371
|
-
if (c === BACKTICK || c === NEWLINE)
|
|
372
|
-
break;
|
|
373
|
-
state.pos++;
|
|
374
|
-
}
|
|
375
|
-
accumulate_text(state, state.buffer.slice(start, state.pos), offset(state, start));
|
|
376
|
-
state.prev_char = state.buffer.charCodeAt(state.pos - 1);
|
|
377
|
-
state.column += state.pos - start;
|
|
378
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Markdown link `[text](url)` handlers and HTML-style tag handlers
|
|
3
|
-
* (`<tag>`, `<Tag />`, `</tag>`) for the streaming mdz parser.
|
|
4
|
-
*
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
|
-
import { type MdzStreamParserState, type TryResult } from './mdz_stream_parser_state.js';
|
|
8
|
-
export declare const try_link_open: (state: MdzStreamParserState) => boolean;
|
|
9
|
-
/**
|
|
10
|
-
* Revert an optimistic Link container to its literal `[` and consume the
|
|
11
|
-
* `]` at `bracket_pos` as text. Used whenever a `]` doesn't form a valid
|
|
12
|
-
* `[text](url)` — missing `(`, empty url, invalid url char, etc.
|
|
13
|
-
*/
|
|
14
|
-
export declare const abort_link_to_text: (state: MdzStreamParserState, link_stack_idx: number, bracket_pos: number) => void;
|
|
15
|
-
/**
|
|
16
|
-
* Try to complete a link after seeing `]`.
|
|
17
|
-
* Returns false if more input is needed (leaves pos at `]`).
|
|
18
|
-
*/
|
|
19
|
-
export declare const try_complete_link: (state: MdzStreamParserState, link_stack_idx: number) => boolean;
|
|
20
|
-
export declare const try_tag_open: (state: MdzStreamParserState) => TryResult;
|
|
21
|
-
export declare const try_close_tag: (state: MdzStreamParserState) => TryResult;
|
|
22
|
-
//# sourceMappingURL=mdz_stream_parser_link.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mdz_stream_parser_link.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_link.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,SAAS,EASd,MAAM,8BAA8B,CAAC;AAItC,eAAO,MAAM,aAAa,GAAI,OAAO,oBAAoB,KAAG,OAa3D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,oBAAoB,EAC3B,gBAAgB,MAAM,EACtB,aAAa,MAAM,KACjB,IAUF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,EAAE,gBAAgB,MAAM,KAAG,OAwEvF,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,oBAAoB,KAAG,SAsE1D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,oBAAoB,KAAG,SA0C3D,CAAC"}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Markdown link `[text](url)` handlers and HTML-style tag handlers
|
|
3
|
-
* (`<tag>`, `<Tag />`, `</tag>`) for the streaming mdz parser.
|
|
4
|
-
*
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
|
-
import { A_UPPER, LEFT_BRACKET, LEFT_PAREN, NEWLINE, RIGHT_ANGLE, RIGHT_BRACKET, RIGHT_PAREN, SLASH, SPACE, Z_UPPER, is_letter, is_tag_name_char, is_valid_path_char, mdz_is_safe_reference, mdz_is_url, } from './mdz_helpers.js';
|
|
8
|
-
import { accumulate_text, alloc_id, emit, ensure_paragraph, flush_text, offset, push_stack_entry, revert_above, } from './mdz_stream_parser_state.js';
|
|
9
|
-
// -- Links --
|
|
10
|
-
export const try_link_open = (state) => {
|
|
11
|
-
flush_text(state);
|
|
12
|
-
ensure_paragraph(state);
|
|
13
|
-
const id = alloc_id(state);
|
|
14
|
-
const link_start = offset(state);
|
|
15
|
-
emit(state, { type: 'open', id, node_type: 'Link', start: link_start });
|
|
16
|
-
push_stack_entry(state, id, 'Link', link_start, true, '[');
|
|
17
|
-
state.active_text_id = null;
|
|
18
|
-
state.pos++;
|
|
19
|
-
state.column++;
|
|
20
|
-
state.prev_char = LEFT_BRACKET;
|
|
21
|
-
return true;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Revert an optimistic Link container to its literal `[` and consume the
|
|
25
|
-
* `]` at `bracket_pos` as text. Used whenever a `]` doesn't form a valid
|
|
26
|
-
* `[text](url)` — missing `(`, empty url, invalid url char, etc.
|
|
27
|
-
*/
|
|
28
|
-
export const abort_link_to_text = (state, link_stack_idx, bracket_pos) => {
|
|
29
|
-
flush_text(state);
|
|
30
|
-
revert_above(state, link_stack_idx);
|
|
31
|
-
const entry = state.stack.pop();
|
|
32
|
-
emit(state, { type: 'revert', id: entry.id, replacement_text: '[', start: entry.start });
|
|
33
|
-
state.active_text_id = null;
|
|
34
|
-
accumulate_text(state, ']', offset(state, bracket_pos));
|
|
35
|
-
state.pos = bracket_pos + 1;
|
|
36
|
-
state.column++;
|
|
37
|
-
state.prev_char = RIGHT_BRACKET;
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Try to complete a link after seeing `]`.
|
|
41
|
-
* Returns false if more input is needed (leaves pos at `]`).
|
|
42
|
-
*/
|
|
43
|
-
export const try_complete_link = (state, link_stack_idx) => {
|
|
44
|
-
const bracket_pos = state.pos;
|
|
45
|
-
// need at least one char after ]
|
|
46
|
-
if (bracket_pos + 1 >= state.buffer.length)
|
|
47
|
-
return false;
|
|
48
|
-
const after_bracket = state.buffer.charCodeAt(bracket_pos + 1);
|
|
49
|
-
if (after_bracket !== LEFT_PAREN) {
|
|
50
|
-
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
// found ]( — scan for )
|
|
54
|
-
let i = bracket_pos + 2; // past ](
|
|
55
|
-
while (i < state.buffer.length) {
|
|
56
|
-
const c = state.buffer.charCodeAt(i);
|
|
57
|
-
if (c === RIGHT_PAREN) {
|
|
58
|
-
const reference = state.buffer.slice(bracket_pos + 2, i);
|
|
59
|
-
// validate reference: non-empty and only valid path chars
|
|
60
|
-
if (!reference.trim()) {
|
|
61
|
-
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
let valid = true;
|
|
65
|
-
for (let j = 0; j < reference.length; j++) {
|
|
66
|
-
if (!is_valid_path_char(reference.charCodeAt(j))) {
|
|
67
|
-
valid = false;
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (!valid) {
|
|
72
|
-
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
// Reject unsafe protocols (javascript:, data:, etc.) at parse time.
|
|
76
|
-
// `is_valid_path_char` permits `:` so we have to filter explicitly.
|
|
77
|
-
if (!mdz_is_safe_reference(reference)) {
|
|
78
|
-
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
// success — close the link with reference
|
|
82
|
-
flush_text(state);
|
|
83
|
-
revert_above(state, link_stack_idx);
|
|
84
|
-
const entry = state.stack.pop();
|
|
85
|
-
const link_type = mdz_is_url(reference) ? 'external' : 'internal';
|
|
86
|
-
emit(state, {
|
|
87
|
-
type: 'close',
|
|
88
|
-
id: entry.id,
|
|
89
|
-
end: offset(state, i + 1),
|
|
90
|
-
reference,
|
|
91
|
-
link_type,
|
|
92
|
-
});
|
|
93
|
-
state.active_text_id = null;
|
|
94
|
-
state.pos = i + 1;
|
|
95
|
-
state.column += i + 1 - bracket_pos;
|
|
96
|
-
state.prev_char = RIGHT_PAREN;
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
if (c === NEWLINE || c === SPACE) {
|
|
100
|
-
// invalid character in URL (simplified check)
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
i++;
|
|
104
|
-
}
|
|
105
|
-
// didn't find ) — need more input or invalid
|
|
106
|
-
if (i >= state.buffer.length)
|
|
107
|
-
return false; // need more input
|
|
108
|
-
// found invalid char before ) — revert
|
|
109
|
-
abort_link_to_text(state, link_stack_idx, bracket_pos);
|
|
110
|
-
return true;
|
|
111
|
-
};
|
|
112
|
-
// -- Tags --
|
|
113
|
-
export const try_tag_open = (state) => {
|
|
114
|
-
const start = state.pos;
|
|
115
|
-
let i = start + 1; // past <
|
|
116
|
-
// tag name must start with letter
|
|
117
|
-
if (i >= state.buffer.length)
|
|
118
|
-
return 'not_match'; // treat as text via fallthrough
|
|
119
|
-
if (!is_letter(state.buffer.charCodeAt(i))) {
|
|
120
|
-
return 'not_match';
|
|
121
|
-
}
|
|
122
|
-
// collect tag name
|
|
123
|
-
const name_start = i;
|
|
124
|
-
while (i < state.buffer.length && is_tag_name_char(state.buffer.charCodeAt(i))) {
|
|
125
|
-
i++;
|
|
126
|
-
}
|
|
127
|
-
if (i >= state.buffer.length)
|
|
128
|
-
return 'need_more';
|
|
129
|
-
const name = state.buffer.slice(name_start, i);
|
|
130
|
-
// skip whitespace
|
|
131
|
-
while (i < state.buffer.length && state.buffer.charCodeAt(i) === SPACE) {
|
|
132
|
-
i++;
|
|
133
|
-
}
|
|
134
|
-
if (i >= state.buffer.length)
|
|
135
|
-
return 'need_more';
|
|
136
|
-
// check for self-closing />
|
|
137
|
-
if (state.buffer.charCodeAt(i) === SLASH &&
|
|
138
|
-
i + 1 < state.buffer.length &&
|
|
139
|
-
state.buffer.charCodeAt(i + 1) === RIGHT_ANGLE) {
|
|
140
|
-
const first_char = name.charCodeAt(0);
|
|
141
|
-
const is_component = first_char >= A_UPPER && first_char <= Z_UPPER;
|
|
142
|
-
const node_type = is_component ? 'Component' : 'Element';
|
|
143
|
-
flush_text(state);
|
|
144
|
-
ensure_paragraph(state);
|
|
145
|
-
const id = alloc_id(state);
|
|
146
|
-
const tag_start = offset(state, start);
|
|
147
|
-
emit(state, { type: 'open', id, node_type, start: tag_start, name });
|
|
148
|
-
emit(state, { type: 'close', id, end: offset(state, i + 2) });
|
|
149
|
-
state.active_text_id = null;
|
|
150
|
-
state.pos = i + 2;
|
|
151
|
-
state.column += i + 2 - start;
|
|
152
|
-
state.prev_char = RIGHT_ANGLE;
|
|
153
|
-
return 'consumed';
|
|
154
|
-
}
|
|
155
|
-
// check for >
|
|
156
|
-
if (state.buffer.charCodeAt(i) !== RIGHT_ANGLE) {
|
|
157
|
-
return 'not_match';
|
|
158
|
-
}
|
|
159
|
-
i++; // past >
|
|
160
|
-
const first_char = name.charCodeAt(0);
|
|
161
|
-
const is_component = first_char >= A_UPPER && first_char <= Z_UPPER;
|
|
162
|
-
const node_type = is_component ? 'Component' : 'Element';
|
|
163
|
-
flush_text(state);
|
|
164
|
-
ensure_paragraph(state);
|
|
165
|
-
const id = alloc_id(state);
|
|
166
|
-
const tag_start = offset(state, start);
|
|
167
|
-
const delimiter = state.buffer.slice(start, i); // e.g., "<Alert>"
|
|
168
|
-
emit(state, { type: 'open', id, node_type, start: tag_start, name });
|
|
169
|
-
push_stack_entry(state, id, node_type, tag_start, true, delimiter, name);
|
|
170
|
-
state.active_text_id = null;
|
|
171
|
-
state.pos = i;
|
|
172
|
-
state.column += i - start;
|
|
173
|
-
state.prev_char = RIGHT_ANGLE;
|
|
174
|
-
return 'consumed';
|
|
175
|
-
};
|
|
176
|
-
export const try_close_tag = (state) => {
|
|
177
|
-
// we're at </
|
|
178
|
-
let i = state.pos + 2; // past </
|
|
179
|
-
if (i >= state.buffer.length)
|
|
180
|
-
return 'need_more';
|
|
181
|
-
// collect tag name
|
|
182
|
-
const name_start = i;
|
|
183
|
-
while (i < state.buffer.length && is_tag_name_char(state.buffer.charCodeAt(i))) {
|
|
184
|
-
i++;
|
|
185
|
-
}
|
|
186
|
-
if (i >= state.buffer.length)
|
|
187
|
-
return 'need_more';
|
|
188
|
-
if (state.buffer.charCodeAt(i) !== RIGHT_ANGLE)
|
|
189
|
-
return 'not_match';
|
|
190
|
-
const name = state.buffer.slice(name_start, i);
|
|
191
|
-
i++; // past >
|
|
192
|
-
// find matching open tag on stack
|
|
193
|
-
let found_idx = -1;
|
|
194
|
-
for (let j = state.stack.length - 1; j >= 0; j--) {
|
|
195
|
-
const entry = state.stack[j];
|
|
196
|
-
if ((entry.node_type === 'Element' || entry.node_type === 'Component') &&
|
|
197
|
-
entry.tag_name === name) {
|
|
198
|
-
found_idx = j;
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
if (entry.node_type === 'Paragraph' || entry.node_type === 'Heading')
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
if (found_idx === -1)
|
|
205
|
-
return 'not_match';
|
|
206
|
-
flush_text(state);
|
|
207
|
-
revert_above(state, found_idx);
|
|
208
|
-
const entry = state.stack.pop();
|
|
209
|
-
emit(state, { type: 'close', id: entry.id, end: offset(state, i) });
|
|
210
|
-
state.active_text_id = null;
|
|
211
|
-
state.column += i - state.pos;
|
|
212
|
-
state.pos = i;
|
|
213
|
-
state.prev_char = RIGHT_ANGLE;
|
|
214
|
-
return 'consumed';
|
|
215
|
-
};
|