@ng-native/components 0.14.0 → 0.15.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.
@@ -0,0 +1,282 @@
1
+ /**
2
+ * A Markdown source that grows as it streams in, lexed a chunk at a time: the blocks before the
3
+ * last one are kept, only the last is lexed again, and syntax still open at its end is held back
4
+ * until it closes, so a half-written code span, emphasis, link, image, table row or code fence
5
+ * line never shows as the characters it is written with.
6
+ *
7
+ * Plain functions and a class with no Angular in them, beside `markdown-blocks.ts`.
8
+ */
9
+ import { Lexer } from 'marked';
10
+ /** Where the last token in `tokens` that is not blank space is, or -1. */
11
+ function lastIndexOfBlock(tokens) {
12
+ let at = tokens.length - 1;
13
+ while (at >= 0 && tokens[at].type === 'space')
14
+ at--;
15
+ return at;
16
+ }
17
+ /** The last block in `tokens` that is not blank space, inside the last quote or list item. */
18
+ function lastBlock(tokens) {
19
+ const last = tokens[lastIndexOfBlock(tokens)];
20
+ if (last?.type === 'list') {
21
+ const item = last.items.at(-1);
22
+ return (item && lastBlock(item.tokens)) ?? last;
23
+ }
24
+ if (last?.type === 'blockquote')
25
+ return lastBlock(last.tokens) ?? last;
26
+ return last;
27
+ }
28
+ const WHITESPACE = /\s/;
29
+ const WORD = /[\p{L}\p{N}]/u;
30
+ const PUNCTUATION = /[\p{P}\p{S}]/u;
31
+ const DELIMITERS = /`+|\*+|_+|~+/g;
32
+ /**
33
+ * The text tokens in `inline`, in order, each found in `tail` after the one before it, starting
34
+ * from `from`. A link or an image is passed over whole, as it closed.
35
+ */
36
+ function leavesOf(inline, tail, from, leaves = []) {
37
+ let cursor = from;
38
+ for (const token of inline) {
39
+ const at = tail.indexOf(token.raw, cursor);
40
+ const children = token.tokens;
41
+ if (token.type === 'text' && !children?.length)
42
+ leaves.push({ raw: token.raw, at });
43
+ else if (children && token.type !== 'link' && token.type !== 'image') {
44
+ leavesOf(children, tail, at < 0 ? cursor : at, leaves);
45
+ }
46
+ if (at >= 0)
47
+ cursor = at + token.raw.length;
48
+ }
49
+ return leaves;
50
+ }
51
+ /**
52
+ * Where the first delimiter in a text token at `at` in `tail` is that could still open a code span
53
+ * or an emphasis. A backtick left as text always could. An asterisk, underscore or tilde could
54
+ * when what follows is not a space, or has not arrived yet, and is not punctuation straight after
55
+ * a letter or digit; an underscore inside a word cannot. What is before and after the delimiter
56
+ * is read from `tail`, since a text token can end where another token starts.
57
+ */
58
+ function openerIn(raw, at, tail) {
59
+ for (const match of raw.matchAll(DELIMITERS)) {
60
+ const from = at + match.index;
61
+ const next = tail[from + match[0].length];
62
+ if (canOpen(match[0], tail[from - 1], next))
63
+ return from;
64
+ }
65
+ return null;
66
+ }
67
+ /** Whether a delimiter run, between `before` and `next`, could open a code span or an emphasis. */
68
+ function canOpen(run, before, next) {
69
+ if (run[0] === '`')
70
+ return true;
71
+ if (next !== undefined && WHITESPACE.test(next))
72
+ return false;
73
+ const afterWord = before !== undefined && WORD.test(before);
74
+ if (afterWord && next !== undefined && PUNCTUATION.test(next))
75
+ return false;
76
+ if (run[0] === '_' && afterWord)
77
+ return false;
78
+ return !(run[0] === '~' && run.length > 2);
79
+ }
80
+ /** Where a fenced code block's half-written fence line starts, or null when it has none. */
81
+ function heldFence(block, tail) {
82
+ const fence = /^ {0,3}(`{3,}|~{3,})/.exec(block.raw);
83
+ if (!fence)
84
+ return null;
85
+ const lineStart = tail.lastIndexOf('\n') + 1;
86
+ // An opening fence whose info string is still arriving.
87
+ if (!block.raw.includes('\n'))
88
+ return lineStart;
89
+ // A closing fence, or a line of the fence's own marker that may yet grow into one: a run too
90
+ // short to close it can grow only while nothing follows it. The block's own last line, since in
91
+ // a quote or a list item the source line starts with their markers.
92
+ const closing = fence[1][0] === '`' ? /^ {0,3}(`+)(\s*)$/ : /^ {0,3}(~+)(\s*)$/;
93
+ const last = closing.exec(block.raw.slice(block.raw.lastIndexOf('\n') + 1));
94
+ const grows = last && (!last[2] || last[1].length >= fence[1].length);
95
+ return grows && !tail.endsWith('\n') ? lineStart : null;
96
+ }
97
+ /**
98
+ * Where a paragraph that starts like a table row starts, while it is a header row whose delimiter
99
+ * row has not arrived, or is arriving, and so a table once it does.
100
+ */
101
+ function heldHeaderRow(block, tail) {
102
+ const lines = block.raw.trimEnd().split('\n');
103
+ // A header row has a pipe, leading or not, as in marked's table syntax.
104
+ if (!lines[0].includes('|'))
105
+ return null;
106
+ if (lines.length > 2)
107
+ return null;
108
+ // A second line that has ended, or that can no longer be a delimiter row, made no table.
109
+ if (lines.length === 2 && (tail.endsWith('\n') || !/^[\s|:-]*$/.test(lines[1])))
110
+ return null;
111
+ const first = tail.lastIndexOf(lines[0].trim());
112
+ return first < 0 ? null : tail.lastIndexOf('\n', first - 1) + 1;
113
+ }
114
+ /**
115
+ * From where in `tail` a bracket starts an unfinished link: a label still open, a label that a
116
+ * target may yet follow, or a target still open. Each has at most one closing bracket after its
117
+ * opening one, so the two last closing brackets in `tail` bound where it can start.
118
+ */
119
+ function unfinishedLinksFrom(tail) {
120
+ const close = tail.lastIndexOf(']');
121
+ if (close < 0)
122
+ return 0;
123
+ const after = tail.slice(close + 1);
124
+ const open = after === '' || after[0] === '[' || (after[0] === '(' && !after.includes(')'));
125
+ return open ? tail.lastIndexOf(']', close - 1) + 1 : close + 1;
126
+ }
127
+ /**
128
+ * Where the first unfinished link, code span or emphasis in `inline` starts in `tail`, found in
129
+ * one pass over its text tokens. Cutting at the first, rather than the last, means the source is
130
+ * lexed again only when the cut opens a span that closed after it, not once for each delimiter
131
+ * that never closes.
132
+ */
133
+ function heldInline(inline, tail, start) {
134
+ const leaves = leavesOf(inline, tail, start);
135
+ const links = unfinishedLinksFrom(tail);
136
+ for (const { raw, at } of leaves) {
137
+ if (at < 0)
138
+ continue;
139
+ const opener = openerIn(raw, at, tail);
140
+ for (let bracket = raw.indexOf('['); bracket >= 0; bracket = raw.indexOf('[', bracket + 1)) {
141
+ const from = at + bracket;
142
+ if (opener !== null && from > opener)
143
+ break;
144
+ if (from >= links)
145
+ return tail[from - 1] === '!' ? from - 1 : from;
146
+ }
147
+ if (opener !== null)
148
+ return opener;
149
+ }
150
+ return null;
151
+ }
152
+ /** Where `tail` is cut so its last block shows nothing half written, or null to show all of it. */
153
+ function holdBackAt(tail, tokens) {
154
+ const block = lastBlock(tokens);
155
+ if (!block)
156
+ return null;
157
+ if (block.type === 'code')
158
+ return heldFence(block, tail);
159
+ if (block.type === 'table')
160
+ return tail.endsWith('\n') ? null : tail.lastIndexOf('\n') + 1;
161
+ const header = block.type === 'paragraph' ? heldHeaderRow(block, tail) : null;
162
+ if (header !== null)
163
+ return header;
164
+ const inline = block.tokens;
165
+ return inline ? heldInline(inline, tail, floorOf(tail, tokens)) : null;
166
+ }
167
+ /**
168
+ * Where the last block starts in `tail`, inside a quote or a list item too, found by its first
169
+ * line since there each source line starts with their markers.
170
+ */
171
+ function floorOf(tail, tokens) {
172
+ const block = lastBlock(tokens);
173
+ if (!block)
174
+ return tail.length;
175
+ // Its whole source when that is in the tail as it is, since its first line can come again.
176
+ const whole = tail.lastIndexOf(block.raw.trimEnd());
177
+ return Math.max(0, whole >= 0 ? whole : tail.lastIndexOf(block.raw.split('\n')[0]));
178
+ }
179
+ /** A line's quote and list item markers, up to where the block they hold starts. */
180
+ const MARKERS = /^(?:[ \t]*(?:>|[-+*](?=[ \t])|\d{1,9}[.)](?=[ \t])))*[ \t]*$/;
181
+ /**
182
+ * Where the markers before `at` on its line start, when only markers are before it, or `at`. A
183
+ * block held back whole takes its markers with it, or a list item marker left behind would join
184
+ * the paragraph before it.
185
+ */
186
+ function markersStart(tail, at) {
187
+ const line = tail.lastIndexOf('\n', at - 1) + 1;
188
+ return MARKERS.test(tail.slice(line, at)) ? line : at;
189
+ }
190
+ /**
191
+ * `tail` lexed, cut back until nothing at its end is half written. Only its last block is cut:
192
+ * once a cut reaches where that block starts, the blocks before it, in its quote or list item
193
+ * too, are drawn as they are.
194
+ */
195
+ function lexHeldBack(tail, links) {
196
+ let floor;
197
+ for (;;) {
198
+ const lexer = new Lexer();
199
+ Object.assign(lexer.tokens.links, links);
200
+ const tokens = lexer.lex(tail);
201
+ floor ??= floorOf(tail, tokens);
202
+ const cut = floor < tail.length ? holdBackAt(tail, tokens) : null;
203
+ if (cut === null || cut >= tail.length)
204
+ return { tokens, links: tokens.links };
205
+ tail = tail.slice(0, cut > floor ? cut : markersStart(tail, floor));
206
+ }
207
+ }
208
+ /** Adds the link references `token` defines, in a quote or a list item too, as marked does. */
209
+ function addDefinitions(token, links) {
210
+ if (token.type === 'def') {
211
+ const def = token;
212
+ links[def.tag] ??= { href: def.href, title: def.title };
213
+ }
214
+ const inner = token.type === 'list'
215
+ ? token.items
216
+ : token.type === 'blockquote' || token.type === 'list_item'
217
+ ? token.tokens
218
+ : undefined;
219
+ for (const child of inner ?? [])
220
+ addDefinitions(child, links);
221
+ }
222
+ function sameLinks(next, previous) {
223
+ const names = Object.keys(next);
224
+ if (names.length !== Object.keys(previous).length)
225
+ return false;
226
+ return names.every((name) => previous[name]?.href === next[name].href && previous[name]?.title === next[name].title);
227
+ }
228
+ /**
229
+ * Lexes a source that grows, keeping the blocks before the last one from the lex before. A source
230
+ * that does not start with the one before is lexed whole, as is one whose new tail defines a link
231
+ * reference or changes one, since that can change a block already kept.
232
+ */
233
+ export class MarkdownStream {
234
+ source = '';
235
+ /** The blocks before the last one, whose raw text is the start of the source exactly. */
236
+ settled = [];
237
+ /** How much of the source the settled blocks are. */
238
+ settledLength = 0;
239
+ /** The link references the settled blocks define. */
240
+ settledLinks = {};
241
+ /** The link references the whole source defines. */
242
+ links = {};
243
+ lex(text) {
244
+ const source = text.replace(/\r\n?/g, '\n');
245
+ const grown = source.startsWith(this.source);
246
+ let settled = grown ? this.settled : [];
247
+ let offset = grown ? this.settledLength : 0;
248
+ let links = grown ? this.settledLinks : {};
249
+ let tail = lexHeldBack(source.slice(offset), links);
250
+ if (settled.length && !sameLinks(tail.links, this.links)) {
251
+ settled = [];
252
+ offset = 0;
253
+ links = {};
254
+ tail = lexHeldBack(source, links);
255
+ }
256
+ this.settle(source, settled, offset, links, tail.tokens);
257
+ this.source = source;
258
+ this.links = tail.links;
259
+ return [...settled, ...tail.tokens];
260
+ }
261
+ /**
262
+ * Keeps the tail's blocks before its last one as settled, as far as their raw text is the
263
+ * source's: marked trims some blocks' raw text, and the lex after starts again from there.
264
+ */
265
+ settle(source, settled, offset, links, tail) {
266
+ const last = lastIndexOfBlock(tail);
267
+ const kept = [...settled];
268
+ const keptLinks = { ...links };
269
+ for (let i = 0; i < last; i++) {
270
+ const token = tail[i];
271
+ if (!source.startsWith(token.raw, offset))
272
+ break;
273
+ kept.push(token);
274
+ offset += token.raw.length;
275
+ addDefinitions(token, keptLinks);
276
+ }
277
+ this.settled = kept;
278
+ this.settledLength = offset;
279
+ this.settledLinks = keptLinks;
280
+ }
281
+ }
282
+ //# sourceMappingURL=markdown-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-stream.js","sourceRoot":"","sources":["../src/markdown-stream.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A Markdown source that grows as it streams in, lexed a chunk at a time: the blocks before the\n * last one are kept, only the last is lexed again, and syntax still open at its end is held back\n * until it closes, so a half-written code span, emphasis, link, image, table row or code fence\n * line never shows as the characters it is written with.\n *\n * Plain functions and a class with no Angular in them, beside `markdown-blocks.ts`.\n */\nimport { Lexer, type Token, type Tokens, type TokensList } from 'marked';\n\ntype Links = TokensList['links'];\n\n/** Where the last token in `tokens` that is not blank space is, or -1. */\nfunction lastIndexOfBlock(tokens: readonly Token[]): number {\n let at = tokens.length - 1;\n while (at >= 0 && tokens[at]!.type === 'space') at--;\n return at;\n}\n\n/** The last block in `tokens` that is not blank space, inside the last quote or list item. */\nfunction lastBlock(tokens: readonly Token[]): Token | undefined {\n const last = tokens[lastIndexOfBlock(tokens)];\n if (last?.type === 'list') {\n const item = (last as Tokens.List).items.at(-1);\n return (item && lastBlock(item.tokens)) ?? last;\n }\n if (last?.type === 'blockquote') return lastBlock((last as Tokens.Blockquote).tokens) ?? last;\n return last;\n}\n\nconst WHITESPACE = /\\s/;\nconst WORD = /[\\p{L}\\p{N}]/u;\nconst PUNCTUATION = /[\\p{P}\\p{S}]/u;\nconst DELIMITERS = /`+|\\*+|_+|~+/g;\n\n/** A text token marked left as text, and where it is in the tail, or -1 when it was not found. */\ninterface Leaf {\n readonly raw: string;\n readonly at: number;\n}\n\n/**\n * The text tokens in `inline`, in order, each found in `tail` after the one before it, starting\n * from `from`. A link or an image is passed over whole, as it closed.\n */\nfunction leavesOf(inline: readonly Token[], tail: string, from: number, leaves: Leaf[] = []) {\n let cursor = from;\n for (const token of inline) {\n const at = tail.indexOf(token.raw, cursor);\n const children = (token as Tokens.Generic).tokens;\n if (token.type === 'text' && !children?.length) leaves.push({ raw: token.raw, at });\n else if (children && token.type !== 'link' && token.type !== 'image') {\n leavesOf(children, tail, at < 0 ? cursor : at, leaves);\n }\n if (at >= 0) cursor = at + token.raw.length;\n }\n return leaves;\n}\n\n/**\n * Where the first delimiter in a text token at `at` in `tail` is that could still open a code span\n * or an emphasis. A backtick left as text always could. An asterisk, underscore or tilde could\n * when what follows is not a space, or has not arrived yet, and is not punctuation straight after\n * a letter or digit; an underscore inside a word cannot. What is before and after the delimiter\n * is read from `tail`, since a text token can end where another token starts.\n */\nfunction openerIn(raw: string, at: number, tail: string): number | null {\n for (const match of raw.matchAll(DELIMITERS)) {\n const from = at + match.index;\n const next = tail[from + match[0].length];\n if (canOpen(match[0], tail[from - 1], next)) return from;\n }\n return null;\n}\n\n/** Whether a delimiter run, between `before` and `next`, could open a code span or an emphasis. */\nfunction canOpen(run: string, before: string | undefined, next: string | undefined) {\n if (run[0] === '`') return true;\n if (next !== undefined && WHITESPACE.test(next)) return false;\n const afterWord = before !== undefined && WORD.test(before);\n if (afterWord && next !== undefined && PUNCTUATION.test(next)) return false;\n if (run[0] === '_' && afterWord) return false;\n return !(run[0] === '~' && run.length > 2);\n}\n\n/** Where a fenced code block's half-written fence line starts, or null when it has none. */\nfunction heldFence(block: Token, tail: string): number | null {\n const fence = /^ {0,3}(`{3,}|~{3,})/.exec(block.raw);\n if (!fence) return null;\n const lineStart = tail.lastIndexOf('\\n') + 1;\n // An opening fence whose info string is still arriving.\n if (!block.raw.includes('\\n')) return lineStart;\n // A closing fence, or a line of the fence's own marker that may yet grow into one: a run too\n // short to close it can grow only while nothing follows it. The block's own last line, since in\n // a quote or a list item the source line starts with their markers.\n const closing = fence[1]![0] === '`' ? /^ {0,3}(`+)(\\s*)$/ : /^ {0,3}(~+)(\\s*)$/;\n const last = closing.exec(block.raw.slice(block.raw.lastIndexOf('\\n') + 1));\n const grows = last && (!last[2] || last[1]!.length >= fence[1]!.length);\n return grows && !tail.endsWith('\\n') ? lineStart : null;\n}\n\n/**\n * Where a paragraph that starts like a table row starts, while it is a header row whose delimiter\n * row has not arrived, or is arriving, and so a table once it does.\n */\nfunction heldHeaderRow(block: Token, tail: string): number | null {\n const lines = block.raw.trimEnd().split('\\n');\n // A header row has a pipe, leading or not, as in marked's table syntax.\n if (!lines[0]!.includes('|')) return null;\n if (lines.length > 2) return null;\n // A second line that has ended, or that can no longer be a delimiter row, made no table.\n if (lines.length === 2 && (tail.endsWith('\\n') || !/^[\\s|:-]*$/.test(lines[1]!))) return null;\n const first = tail.lastIndexOf(lines[0]!.trim());\n return first < 0 ? null : tail.lastIndexOf('\\n', first - 1) + 1;\n}\n\n/**\n * From where in `tail` a bracket starts an unfinished link: a label still open, a label that a\n * target may yet follow, or a target still open. Each has at most one closing bracket after its\n * opening one, so the two last closing brackets in `tail` bound where it can start.\n */\nfunction unfinishedLinksFrom(tail: string): number {\n const close = tail.lastIndexOf(']');\n if (close < 0) return 0;\n const after = tail.slice(close + 1);\n const open = after === '' || after[0] === '[' || (after[0] === '(' && !after.includes(')'));\n return open ? tail.lastIndexOf(']', close - 1) + 1 : close + 1;\n}\n\n/**\n * Where the first unfinished link, code span or emphasis in `inline` starts in `tail`, found in\n * one pass over its text tokens. Cutting at the first, rather than the last, means the source is\n * lexed again only when the cut opens a span that closed after it, not once for each delimiter\n * that never closes.\n */\nfunction heldInline(inline: readonly Token[], tail: string, start: number): number | null {\n const leaves = leavesOf(inline, tail, start);\n const links = unfinishedLinksFrom(tail);\n for (const { raw, at } of leaves) {\n if (at < 0) continue;\n const opener = openerIn(raw, at, tail);\n for (let bracket = raw.indexOf('['); bracket >= 0; bracket = raw.indexOf('[', bracket + 1)) {\n const from = at + bracket;\n if (opener !== null && from > opener) break;\n if (from >= links) return tail[from - 1] === '!' ? from - 1 : from;\n }\n if (opener !== null) return opener;\n }\n return null;\n}\n\n/** Where `tail` is cut so its last block shows nothing half written, or null to show all of it. */\nfunction holdBackAt(tail: string, tokens: readonly Token[]): number | null {\n const block = lastBlock(tokens);\n if (!block) return null;\n if (block.type === 'code') return heldFence(block, tail);\n if (block.type === 'table') return tail.endsWith('\\n') ? null : tail.lastIndexOf('\\n') + 1;\n const header = block.type === 'paragraph' ? heldHeaderRow(block, tail) : null;\n if (header !== null) return header;\n const inline = (block as Tokens.Generic).tokens;\n return inline ? heldInline(inline, tail, floorOf(tail, tokens)) : null;\n}\n\n/**\n * Where the last block starts in `tail`, inside a quote or a list item too, found by its first\n * line since there each source line starts with their markers.\n */\nfunction floorOf(tail: string, tokens: readonly Token[]): number {\n const block = lastBlock(tokens);\n if (!block) return tail.length;\n // Its whole source when that is in the tail as it is, since its first line can come again.\n const whole = tail.lastIndexOf(block.raw.trimEnd());\n return Math.max(0, whole >= 0 ? whole : tail.lastIndexOf(block.raw.split('\\n')[0]!));\n}\n\n/** A line's quote and list item markers, up to where the block they hold starts. */\nconst MARKERS = /^(?:[ \\t]*(?:>|[-+*](?=[ \\t])|\\d{1,9}[.)](?=[ \\t])))*[ \\t]*$/;\n\n/**\n * Where the markers before `at` on its line start, when only markers are before it, or `at`. A\n * block held back whole takes its markers with it, or a list item marker left behind would join\n * the paragraph before it.\n */\nfunction markersStart(tail: string, at: number): number {\n const line = tail.lastIndexOf('\\n', at - 1) + 1;\n return MARKERS.test(tail.slice(line, at)) ? line : at;\n}\n\n/**\n * `tail` lexed, cut back until nothing at its end is half written. Only its last block is cut:\n * once a cut reaches where that block starts, the blocks before it, in its quote or list item\n * too, are drawn as they are.\n */\nfunction lexHeldBack(tail: string, links: Links): { tokens: Token[]; links: Links } {\n let floor: number | undefined;\n for (;;) {\n const lexer = new Lexer();\n Object.assign(lexer.tokens.links, links);\n const tokens = lexer.lex(tail);\n floor ??= floorOf(tail, tokens);\n const cut = floor < tail.length ? holdBackAt(tail, tokens) : null;\n if (cut === null || cut >= tail.length) return { tokens, links: tokens.links };\n tail = tail.slice(0, cut > floor ? cut : markersStart(tail, floor));\n }\n}\n\n/** Adds the link references `token` defines, in a quote or a list item too, as marked does. */\nfunction addDefinitions(token: Token, links: Links) {\n if (token.type === 'def') {\n const def = token as Tokens.Def;\n links[def.tag] ??= { href: def.href, title: def.title };\n }\n const inner =\n token.type === 'list'\n ? (token as Tokens.List).items\n : token.type === 'blockquote' || token.type === 'list_item'\n ? (token as Tokens.Generic).tokens\n : undefined;\n for (const child of inner ?? []) addDefinitions(child, links);\n}\n\nfunction sameLinks(next: Links, previous: Links): boolean {\n const names = Object.keys(next);\n if (names.length !== Object.keys(previous).length) return false;\n return names.every(\n (name) =>\n previous[name]?.href === next[name]!.href && previous[name]?.title === next[name]!.title,\n );\n}\n\n/**\n * Lexes a source that grows, keeping the blocks before the last one from the lex before. A source\n * that does not start with the one before is lexed whole, as is one whose new tail defines a link\n * reference or changes one, since that can change a block already kept.\n */\nexport class MarkdownStream {\n private source = '';\n /** The blocks before the last one, whose raw text is the start of the source exactly. */\n private settled: Token[] = [];\n /** How much of the source the settled blocks are. */\n private settledLength = 0;\n /** The link references the settled blocks define. */\n private settledLinks: Links = {};\n /** The link references the whole source defines. */\n private links: Links = {};\n\n lex(text: string): Token[] {\n const source = text.replace(/\\r\\n?/g, '\\n');\n const grown = source.startsWith(this.source);\n let settled = grown ? this.settled : [];\n let offset = grown ? this.settledLength : 0;\n let links = grown ? this.settledLinks : {};\n let tail = lexHeldBack(source.slice(offset), links);\n if (settled.length && !sameLinks(tail.links, this.links)) {\n settled = [];\n offset = 0;\n links = {};\n tail = lexHeldBack(source, links);\n }\n this.settle(source, settled, offset, links, tail.tokens);\n this.source = source;\n this.links = tail.links;\n return [...settled, ...tail.tokens];\n }\n\n /**\n * Keeps the tail's blocks before its last one as settled, as far as their raw text is the\n * source's: marked trims some blocks' raw text, and the lex after starts again from there.\n */\n private settle(source: string, settled: Token[], offset: number, links: Links, tail: Token[]) {\n const last = lastIndexOfBlock(tail);\n const kept = [...settled];\n const keptLinks = { ...links };\n for (let i = 0; i < last; i++) {\n const token = tail[i]!;\n if (!source.startsWith(token.raw, offset)) break;\n kept.push(token);\n offset += token.raw.length;\n addDefinitions(token, keptLinks);\n }\n this.settled = kept;\n this.settledLength = offset;\n this.settledLinks = keptLinks;\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { type Token } from 'marked';
2
2
  import type { ImageLoadEvent } from './events.ts';
3
- import { type MarkdownBlock, type MarkdownElement, type MarkdownInline } from './markdown-blocks.ts';
3
+ import { blockKey, type MarkdownBlock, type MarkdownElement, type MarkdownInline } from './markdown-blocks.ts';
4
4
  import * as i0 from "@angular/core";
5
5
  export type { MarkdownElement } from './markdown-blocks.ts';
6
6
  /** Classes for the elements a document is drawn with, each one in place of the default. */
@@ -33,6 +33,15 @@ type MarkdownLink = Extract<MarkdownInline, {
33
33
  * dark. `classes` replaces one by its element's name, so a global stylesheet or Tailwind styles it
34
34
  * instead: `{ h1: 'text-3xl font-bold' }`. Put the default back beside your own to keep both.
35
35
  *
36
+ * For a source that grows as it streams in, an answer arriving a few words at a time, set
37
+ * `streaming` until it is complete. The blocks before the last one are then kept as they are and
38
+ * only the last is lexed and converted again, unless a chunk defines or changes a link reference,
39
+ * which can change a block already kept, and the whole source is lexed again. Syntax still open at its end, a code span, an emphasis, a link,
40
+ * an image or a table row, is held back until it closes. Of a code fence only an unfinished
41
+ * opening line is held back, and its code then streams as it arrives. Clearing `streaming` draws
42
+ * the whole source as written, and `tokens` are always drawn as given. A block that did not
43
+ * change keeps its views, streaming or not.
44
+ *
36
45
  * A pressed link is `(linkPress)`. An absolute target is then opened with `DeepLinks.open` unless
37
46
  * a handler calls `preventDefault()`; a relative one is only reported, for the app to route.
38
47
  */
@@ -44,20 +53,38 @@ export declare class Markdown {
44
53
  readonly source: import("@angular/core").InputSignal<string | undefined>;
45
54
  /** Tokens `marked.lexer` made already, drawn in place of `source`. */
46
55
  readonly tokens: import("@angular/core").InputSignal<readonly Token[] | undefined>;
56
+ /**
57
+ * Whether `source` is still arriving, a chunk at a time. While it is, only its last block is
58
+ * lexed again, unless a link reference changes, and syntax still open at its end is held back.
59
+ * See the class note.
60
+ */
61
+ readonly streaming: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
47
62
  /** A class for an element, in place of its default. See the class note. */
48
63
  readonly classes: import("@angular/core").InputSignal<Partial<Readonly<Record<MarkdownElement, string>>>>;
49
64
  /** A link was pressed. See the class note. */
50
65
  readonly linkPress: import("@angular/core").OutputEmitterRef<MarkdownLinkPress>;
51
- protected readonly blocks: import("@angular/core").Signal<MarkdownBlock[]>;
66
+ private readonly stream;
67
+ protected readonly blockKey: typeof blockKey;
68
+ /**
69
+ * The blocks each token the stream kept was drawn as. A kept token is the same object from one
70
+ * chunk to the next, so its blocks are not converted or compared again; a source lexed whole
71
+ * makes new tokens, and so new blocks.
72
+ */
73
+ private readonly streamed;
74
+ /** The document's blocks, with each one that did not change kept, so it is not drawn again. */
75
+ protected readonly blocks: import("@angular/core").WritableSignal<readonly MarkdownBlock[]>;
52
76
  private readonly classOf;
53
77
  /**
54
78
  * Each image's width over its height, once it has loaded, so it is drawn at the page's width and
55
79
  * its own shape. An image the document no longer has is forgotten when the document changes.
56
80
  */
57
81
  protected readonly ratios: import("@angular/core").WritableSignal<ReadonlyMap<string, number>>;
82
+ private lex;
83
+ /** `tokens`' blocks, converting and sharing only those of tokens the stream had not kept. */
84
+ private streamedBlocks;
58
85
  protected classFor(element: MarkdownElement): string;
59
86
  protected sizeImage(src: string, event: Partial<ImageLoadEvent>): void;
60
87
  protected followLink(link: MarkdownLink): void;
61
88
  static ɵfac: i0.ɵɵFactoryDeclaration<Markdown, never>;
62
- static ɵcmp: i0.ɵɵComponentDeclaration<Markdown, "markdown", never, { "source": { "alias": "source"; "required": false; "isSignal": true; }; "tokens": { "alias": "tokens"; "required": false; "isSignal": true; }; "classes": { "alias": "classes"; "required": false; "isSignal": true; }; }, { "linkPress": "linkPress"; }, never, never, true, never>;
89
+ static ɵcmp: i0.ɵɵComponentDeclaration<Markdown, "markdown", never, { "source": { "alias": "source"; "required": false; "isSignal": true; }; "tokens": { "alias": "tokens"; "required": false; "isSignal": true; }; "streaming": { "alias": "streaming"; "required": false; "isSignal": true; }; "classes": { "alias": "classes"; "required": false; "isSignal": true; }; }, { "linkPress": "linkPress"; }, never, never, true, never>;
63
90
  }