@diegopetrucci/pi-annotate-last-message 0.1.8 → 0.1.10
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/.pi-fleet-tested-version +1 -1
- package/README.md +9 -1
- package/index.ts +80 -14
- package/package.json +2 -1
- package/quiet-glimpse.ts +28 -5
- package/theme.ts +247 -0
- package/ui.ts +28 -2
- package/web/app.js +351 -162
- package/web/index.html +153 -24
- package/web/md-renderer.js +458 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/* global globalThis */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TLH annotate-last-message: pure markdown line parser.
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth for the parsing logic. ui.ts reads this file and
|
|
7
|
+
* inlines it as a dedicated script before app.js, so globalThis.__tlhMdRenderer
|
|
8
|
+
* is available when the app script runs. Tests load this file directly in a
|
|
9
|
+
* Node.js vm context (no DOM required).
|
|
10
|
+
*/
|
|
11
|
+
(function (global) {
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
// -------------------------------------------------------------------------
|
|
15
|
+
// Fence-state machine
|
|
16
|
+
//
|
|
17
|
+
// Applied over the full ordered line list before per-line rendering begins.
|
|
18
|
+
// Each line is tagged as: 'plain' | 'fence-open' | 'fence-body' | 'fence-close'
|
|
19
|
+
//
|
|
20
|
+
// Fence-delimiter lines (the ``` or ~~~ lines) are tagged 'fence-open' or
|
|
21
|
+
// 'fence-close'; they are rendered visible but styled as block-edge markers
|
|
22
|
+
// (--mdCodeBlockBorder colour) rather than suppressed. Fence-body lines skip
|
|
23
|
+
// inline markdown processing and receive code-block styling instead.
|
|
24
|
+
//
|
|
25
|
+
// A fenced block that reaches end-of-input without a closing delimiter is
|
|
26
|
+
// left open; all remaining lines are tagged 'fence-body'.
|
|
27
|
+
// -------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
function isFenceDelimiter(text) {
|
|
30
|
+
// CommonMark §4.5: opening/closing fences allow at most 3 spaces of
|
|
31
|
+
// indentation. A line with 4+ leading spaces is an indented code block
|
|
32
|
+
// and must not be treated as a fence delimiter.
|
|
33
|
+
const m = /^ {0,3}(```+|~~~+)/.exec(text);
|
|
34
|
+
return m ? { char: m[1][0], len: m[1].length } : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* applyFenceState(lineTexts) → Array<{ text: string, lineType: string }>
|
|
39
|
+
*
|
|
40
|
+
* One output entry per input entry; the count is always preserved.
|
|
41
|
+
*/
|
|
42
|
+
function applyFenceState(lineTexts) {
|
|
43
|
+
const result = [];
|
|
44
|
+
let inFence = false;
|
|
45
|
+
let fenceChar = "";
|
|
46
|
+
let fenceLen = 0;
|
|
47
|
+
|
|
48
|
+
for (const text of lineTexts) {
|
|
49
|
+
const fence = isFenceDelimiter(text);
|
|
50
|
+
|
|
51
|
+
if (!inFence) {
|
|
52
|
+
if (fence) {
|
|
53
|
+
inFence = true;
|
|
54
|
+
fenceChar = fence.char;
|
|
55
|
+
fenceLen = fence.len;
|
|
56
|
+
result.push({ text, lineType: "fence-open" });
|
|
57
|
+
} else {
|
|
58
|
+
result.push({ text, lineType: "plain" });
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Close if same delimiter character, at least as many chars, and
|
|
62
|
+
// nothing but optional whitespace after the delimiter (CommonMark
|
|
63
|
+
// §4.5: a closing fence must not have trailing non-whitespace).
|
|
64
|
+
const afterDelim = fence ? text.replace(/^ {0,3}(?:```+|~~~+)/, "") : "";
|
|
65
|
+
if (
|
|
66
|
+
fence &&
|
|
67
|
+
fence.char === fenceChar &&
|
|
68
|
+
fence.len >= fenceLen &&
|
|
69
|
+
/^\s*$/.test(afterDelim)
|
|
70
|
+
) {
|
|
71
|
+
inFence = false;
|
|
72
|
+
fenceChar = "";
|
|
73
|
+
fenceLen = 0;
|
|
74
|
+
result.push({ text, lineType: "fence-close" });
|
|
75
|
+
} else {
|
|
76
|
+
result.push({ text, lineType: "fence-body" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// -------------------------------------------------------------------------
|
|
85
|
+
// Line classifier (called only on 'plain'-typed lines)
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* classifyLine(text) → LineInfo
|
|
90
|
+
*
|
|
91
|
+
* Returned shapes:
|
|
92
|
+
* { type: 'blank' }
|
|
93
|
+
* { type: 'heading', level: 1-6, text: string }
|
|
94
|
+
* { type: 'hr' }
|
|
95
|
+
* { type: 'blockquote', text: string }
|
|
96
|
+
* { type: 'ul', indent: string, bullet: string, text: string }
|
|
97
|
+
* { type: 'ol', indent: string, bullet: string, text: string }
|
|
98
|
+
* { type: 'plain', text: string }
|
|
99
|
+
*/
|
|
100
|
+
function classifyLine(text) {
|
|
101
|
+
if (text.trim().length === 0) {
|
|
102
|
+
return { type: "blank" };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const trimmed = text.trimStart();
|
|
106
|
+
const indentLen = text.length - trimmed.length;
|
|
107
|
+
|
|
108
|
+
// CommonMark block-indentation rule: constructs that HIDE or REPLACE
|
|
109
|
+
// source characters (headings hide '#', thematic breaks replace the
|
|
110
|
+
// whole line, blockquotes hide '>') must have at most 3 spaces of
|
|
111
|
+
// leading indentation. At 4+ spaces the line is indented code and
|
|
112
|
+
// must render literally. List markers are exempt: they preserve every
|
|
113
|
+
// source character and only restyle the bullet, so nested-list
|
|
114
|
+
// indentation stays lenient.
|
|
115
|
+
const blockSafe = indentLen <= 3;
|
|
116
|
+
|
|
117
|
+
// ATX heading: # … through ###### …
|
|
118
|
+
// The heading content may optionally end with a closing # sequence.
|
|
119
|
+
if (blockSafe) {
|
|
120
|
+
const headingMatch = /^(#{1,6})\s+(.+?)(?:\s+#+\s*)?$/.exec(trimmed);
|
|
121
|
+
if (headingMatch) {
|
|
122
|
+
return { type: "heading", level: headingMatch[1].length, text: headingMatch[2] };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Thematic break: 3+ dashes, stars, or underscores with optional spaces.
|
|
127
|
+
if (blockSafe) {
|
|
128
|
+
if (/^(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/.test(trimmed)) {
|
|
129
|
+
return { type: "hr" };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Blockquote: > text
|
|
134
|
+
if (blockSafe) {
|
|
135
|
+
const bqMatch = /^>[ \t]?(.*)/.exec(trimmed);
|
|
136
|
+
if (bqMatch) {
|
|
137
|
+
return { type: "blockquote", text: bqMatch[1] };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Unordered list item: optional indent + (- * +) + one or more spaces + text
|
|
142
|
+
const ulMatch = /^(\s*)([-*+])[ \t]+(.*)/.exec(text);
|
|
143
|
+
if (ulMatch) {
|
|
144
|
+
return { type: "ul", indent: ulMatch[1], bullet: ulMatch[2], text: ulMatch[3] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Ordered list item: optional indent + number. + one or more spaces + text
|
|
148
|
+
const olMatch = /^(\s*)(\d+\.)[ \t]+(.*)/.exec(text);
|
|
149
|
+
if (olMatch) {
|
|
150
|
+
return { type: "ol", indent: olMatch[1], bullet: olMatch[2], text: olMatch[3] };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { type: "plain", text };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
// Inline tokenizer
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
function isMarkdownSpecial(ch) {
|
|
161
|
+
return ch === "*" || ch === "_" || ch === "~" || ch === "`" || ch === "[" || ch === "\\";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Try to parse an inline code span starting at position i.
|
|
166
|
+
* Returns { codeText, end } on success, null on failure.
|
|
167
|
+
*/
|
|
168
|
+
function tryParseCodeSpan(text, i) {
|
|
169
|
+
// Count opening backtick run.
|
|
170
|
+
let tickCount = 0;
|
|
171
|
+
while (i + tickCount < text.length && text[i + tickCount] === "`") tickCount++;
|
|
172
|
+
const contentStart = i + tickCount;
|
|
173
|
+
|
|
174
|
+
// Find a closing run of exactly the same length.
|
|
175
|
+
let j = contentStart;
|
|
176
|
+
while (j < text.length) {
|
|
177
|
+
if (text[j] !== "`") {
|
|
178
|
+
j++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
let runLen = 0;
|
|
182
|
+
while (j + runLen < text.length && text[j + runLen] === "`") runLen++;
|
|
183
|
+
if (runLen === tickCount) {
|
|
184
|
+
return { codeText: text.slice(contentStart, j), end: j + tickCount };
|
|
185
|
+
}
|
|
186
|
+
j += runLen;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find the closing position of an emphasis/strikethrough marker.
|
|
193
|
+
* Skips over backslash escapes and inline code spans.
|
|
194
|
+
* For single-char markers (star or underscore) the closer must be an
|
|
195
|
+
* isolated run of exactly 1; for 2- or 3-char markers the run must be
|
|
196
|
+
* exactly that length. Returns -1 if not found.
|
|
197
|
+
*
|
|
198
|
+
* When checkIntraword is true (used for underscore-based delimiters),
|
|
199
|
+
* a candidate closing run that is flanked by alphanumerics on both sides
|
|
200
|
+
* is skipped — CommonMark's intraword-underscore rule.
|
|
201
|
+
*/
|
|
202
|
+
function findEmphasisCloser(text, marker, from, checkIntraword = false) {
|
|
203
|
+
const mLen = marker.length;
|
|
204
|
+
const mChar = marker[0];
|
|
205
|
+
let j = from;
|
|
206
|
+
|
|
207
|
+
while (j < text.length) {
|
|
208
|
+
// Backslash escape: skip the next character.
|
|
209
|
+
if (text[j] === "\\" && j + 1 < text.length) {
|
|
210
|
+
j += 2;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Inline code span: skip its entire content.
|
|
215
|
+
if (text[j] === "`") {
|
|
216
|
+
let tickCount = 0;
|
|
217
|
+
while (j + tickCount < text.length && text[j + tickCount] === "`") tickCount++;
|
|
218
|
+
const closeAt = text.indexOf("`".repeat(tickCount), j + tickCount);
|
|
219
|
+
j = closeAt === -1 ? text.length : closeAt + tickCount;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (text[j] === mChar) {
|
|
224
|
+
// Measure the run length at this position.
|
|
225
|
+
let runLen = 0;
|
|
226
|
+
while (j + runLen < text.length && text[j + runLen] === mChar) runLen++;
|
|
227
|
+
|
|
228
|
+
if (runLen === mLen) {
|
|
229
|
+
if (checkIntraword) {
|
|
230
|
+
// Underscore-based closing delimiter flanked by alphanumerics
|
|
231
|
+
// on both sides is intraword and cannot close emphasis.
|
|
232
|
+
const charBefore = j > 0 ? text[j - 1] : "";
|
|
233
|
+
const charAfter = j + mLen < text.length ? text[j + mLen] : "";
|
|
234
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
235
|
+
j += runLen;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return j;
|
|
240
|
+
}
|
|
241
|
+
// Wrong run length — skip the whole run and keep scanning.
|
|
242
|
+
j += runLen;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
j++;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return -1;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* tokenizeLine(text) → Token[]
|
|
254
|
+
*
|
|
255
|
+
* Parses inline markdown and returns a token tree. Falls back to a single
|
|
256
|
+
* plain-text token if an error is thrown.
|
|
257
|
+
*
|
|
258
|
+
* Token shapes:
|
|
259
|
+
* { type: 'text', text: string }
|
|
260
|
+
* { type: 'code', text: string }
|
|
261
|
+
* { type: 'bold', children: Token[] }
|
|
262
|
+
* { type: 'italic', children: Token[] }
|
|
263
|
+
* { type: 'boldItalic', children: Token[] }
|
|
264
|
+
* { type: 'strikethrough', children: Token[] }
|
|
265
|
+
* { type: 'link', labelTokens: Token[], url: string }
|
|
266
|
+
*/
|
|
267
|
+
function tokenizeLine(text) {
|
|
268
|
+
try {
|
|
269
|
+
return parseInlineSpans(text);
|
|
270
|
+
} catch {
|
|
271
|
+
return [{ type: "text", text }];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseInlineSpans(text) {
|
|
276
|
+
const tokens = [];
|
|
277
|
+
let i = 0;
|
|
278
|
+
let buf = "";
|
|
279
|
+
let bracketMatches = null;
|
|
280
|
+
|
|
281
|
+
// Match raw brackets once per parse call; suffix rescans make runs quadratic.
|
|
282
|
+
function getBracketMatches() {
|
|
283
|
+
if (bracketMatches !== null) return bracketMatches;
|
|
284
|
+
|
|
285
|
+
bracketMatches = new Map();
|
|
286
|
+
const stack = [];
|
|
287
|
+
for (let k = 0; k < text.length; k++) {
|
|
288
|
+
if (text[k] === "[") {
|
|
289
|
+
stack.push(k);
|
|
290
|
+
} else if (text[k] === "]" && stack.length > 0) {
|
|
291
|
+
bracketMatches.set(stack.pop(), k);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return bracketMatches;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function flush() {
|
|
298
|
+
if (buf.length > 0) {
|
|
299
|
+
tokens.push({ type: "text", text: buf });
|
|
300
|
+
buf = "";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
while (i < text.length) {
|
|
305
|
+
const ch = text[i];
|
|
306
|
+
|
|
307
|
+
// Backslash escape: treat next markdown-special character as literal.
|
|
308
|
+
if (ch === "\\" && i + 1 < text.length && isMarkdownSpecial(text[i + 1])) {
|
|
309
|
+
buf += text[i + 1];
|
|
310
|
+
i += 2;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Inline code span.
|
|
315
|
+
if (ch === "`") {
|
|
316
|
+
const result = tryParseCodeSpan(text, i);
|
|
317
|
+
if (result) {
|
|
318
|
+
flush();
|
|
319
|
+
tokens.push({ type: "code", text: result.codeText });
|
|
320
|
+
i = result.end;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
buf += ch;
|
|
324
|
+
i++;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Link: [label](url)
|
|
329
|
+
if (ch === "[") {
|
|
330
|
+
const closeLabel = getBracketMatches().get(i) ?? -1;
|
|
331
|
+
if (closeLabel !== -1 && text[closeLabel + 1] === "(") {
|
|
332
|
+
const urlStart = closeLabel + 2;
|
|
333
|
+
const closeUrl = text.indexOf(")", urlStart);
|
|
334
|
+
if (closeUrl !== -1) {
|
|
335
|
+
flush();
|
|
336
|
+
const labelText = text.slice(i + 1, closeLabel);
|
|
337
|
+
const url = text.slice(urlStart, closeUrl);
|
|
338
|
+
tokens.push({ type: "link", labelTokens: parseInlineSpans(labelText), url });
|
|
339
|
+
i = closeUrl + 1;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
buf += ch;
|
|
344
|
+
i++;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Bold+italic: ***text*** or ___text___
|
|
349
|
+
if (
|
|
350
|
+
(ch === "*" && text[i + 1] === "*" && text[i + 2] === "*") ||
|
|
351
|
+
(ch === "_" && text[i + 1] === "_" && text[i + 2] === "_")
|
|
352
|
+
) {
|
|
353
|
+
// Underscore-based opening: skip if flanked by alphanumerics
|
|
354
|
+
// on both sides (CommonMark intraword-underscore rule).
|
|
355
|
+
if (ch === "_") {
|
|
356
|
+
const charBefore = i > 0 ? text[i - 1] : "";
|
|
357
|
+
const charAfter = i + 3 < text.length ? text[i + 3] : "";
|
|
358
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
359
|
+
buf += ch.repeat(3);
|
|
360
|
+
i += 3;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const marker = ch.repeat(3);
|
|
365
|
+
const closeIdx = findEmphasisCloser(text, marker, i + 3, ch === "_");
|
|
366
|
+
if (closeIdx !== -1) {
|
|
367
|
+
const inner = text.slice(i + 3, closeIdx);
|
|
368
|
+
if (inner.length > 0 && inner[0] !== " " && inner[inner.length - 1] !== " ") {
|
|
369
|
+
flush();
|
|
370
|
+
tokens.push({ type: "boldItalic", children: parseInlineSpans(inner) });
|
|
371
|
+
i = closeIdx + 3;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Bold: **text** or __text__
|
|
378
|
+
if (
|
|
379
|
+
(ch === "*" && text[i + 1] === "*" && text[i + 2] !== "*") ||
|
|
380
|
+
(ch === "_" && text[i + 1] === "_" && text[i + 2] !== "_")
|
|
381
|
+
) {
|
|
382
|
+
// Underscore-based opening: skip if flanked by alphanumerics
|
|
383
|
+
// on both sides (CommonMark intraword-underscore rule).
|
|
384
|
+
if (ch === "_") {
|
|
385
|
+
const charBefore = i > 0 ? text[i - 1] : "";
|
|
386
|
+
const charAfter = i + 2 < text.length ? text[i + 2] : "";
|
|
387
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
388
|
+
buf += ch.repeat(2);
|
|
389
|
+
i += 2;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const marker = ch.repeat(2);
|
|
394
|
+
const closeIdx = findEmphasisCloser(text, marker, i + 2, ch === "_");
|
|
395
|
+
if (closeIdx !== -1) {
|
|
396
|
+
const inner = text.slice(i + 2, closeIdx);
|
|
397
|
+
if (inner.length > 0 && inner[0] !== " " && inner[inner.length - 1] !== " ") {
|
|
398
|
+
flush();
|
|
399
|
+
tokens.push({ type: "bold", children: parseInlineSpans(inner) });
|
|
400
|
+
i = closeIdx + 2;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Strikethrough: ~~text~~
|
|
407
|
+
if (ch === "~" && text[i + 1] === "~") {
|
|
408
|
+
const closeIdx = text.indexOf("~~", i + 2);
|
|
409
|
+
if (closeIdx !== -1) {
|
|
410
|
+
const inner = text.slice(i + 2, closeIdx);
|
|
411
|
+
if (inner.length > 0) {
|
|
412
|
+
flush();
|
|
413
|
+
tokens.push({ type: "strikethrough", children: parseInlineSpans(inner) });
|
|
414
|
+
i = closeIdx + 2;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Italic: *text* or _text_ (single marker, not start of ** or ***)
|
|
421
|
+
if ((ch === "*" && text[i + 1] !== "*") || (ch === "_" && text[i + 1] !== "_")) {
|
|
422
|
+
// Underscore-based opening: skip if flanked by alphanumerics
|
|
423
|
+
// on both sides (CommonMark intraword-underscore rule).
|
|
424
|
+
if (ch === "_") {
|
|
425
|
+
const charBefore = i > 0 ? text[i - 1] : "";
|
|
426
|
+
const charAfter = i + 1 < text.length ? text[i + 1] : "";
|
|
427
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
428
|
+
buf += ch;
|
|
429
|
+
i += 1;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const closeIdx = findEmphasisCloser(text, ch, i + 1, ch === "_");
|
|
434
|
+
if (closeIdx !== -1) {
|
|
435
|
+
const inner = text.slice(i + 1, closeIdx);
|
|
436
|
+
if (inner.length > 0 && inner[0] !== " " && inner[inner.length - 1] !== " ") {
|
|
437
|
+
flush();
|
|
438
|
+
tokens.push({ type: "italic", children: parseInlineSpans(inner) });
|
|
439
|
+
i = closeIdx + 1;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
buf += ch;
|
|
446
|
+
i++;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
flush();
|
|
450
|
+
return tokens;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
global.__tlhMdRenderer = {
|
|
454
|
+
applyFenceState,
|
|
455
|
+
classifyLine,
|
|
456
|
+
tokenizeLine,
|
|
457
|
+
};
|
|
458
|
+
})(globalThis);
|