@kodelyth/zalouser 2026.5.42 → 2026.6.2
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/klaw.plugin.json +286 -3
- package/package.json +19 -6
- package/api.ts +0 -9
- package/channel-plugin-api.ts +0 -3
- package/contract-api.ts +0 -2
- package/doctor-contract-api.ts +0 -1
- package/index.ts +0 -34
- package/runtime-api.ts +0 -62
- package/secret-contract-api.ts +0 -4
- package/setup-entry.ts +0 -9
- package/setup-plugin-api.ts +0 -2
- package/src/accounts.runtime.ts +0 -1
- package/src/accounts.test-mocks.ts +0 -14
- package/src/accounts.test.ts +0 -298
- package/src/accounts.ts +0 -136
- package/src/channel-api.ts +0 -16
- package/src/channel.adapters.ts +0 -432
- package/src/channel.directory.test.ts +0 -59
- package/src/channel.runtime.ts +0 -12
- package/src/channel.sendpayload.test.ts +0 -311
- package/src/channel.setup.test.ts +0 -30
- package/src/channel.setup.ts +0 -12
- package/src/channel.test.ts +0 -424
- package/src/channel.ts +0 -221
- package/src/config-schema.ts +0 -33
- package/src/directory.ts +0 -54
- package/src/doctor-contract.ts +0 -156
- package/src/doctor.test.ts +0 -87
- package/src/doctor.ts +0 -37
- package/src/group-policy.test.ts +0 -61
- package/src/group-policy.ts +0 -83
- package/src/message-sid.test.ts +0 -66
- package/src/message-sid.ts +0 -80
- package/src/monitor.account-scope.test.ts +0 -122
- package/src/monitor.group-gating.test.ts +0 -967
- package/src/monitor.send-mocks.ts +0 -20
- package/src/monitor.ts +0 -1057
- package/src/probe.test.ts +0 -60
- package/src/probe.ts +0 -35
- package/src/qr-temp-file.ts +0 -19
- package/src/reaction.test.ts +0 -19
- package/src/reaction.ts +0 -32
- package/src/runtime.ts +0 -9
- package/src/security-audit.test.ts +0 -83
- package/src/security-audit.ts +0 -71
- package/src/send-receipt.ts +0 -31
- package/src/send.test.ts +0 -424
- package/src/send.ts +0 -280
- package/src/session-route.ts +0 -121
- package/src/setup-core.ts +0 -36
- package/src/setup-surface.test.ts +0 -367
- package/src/setup-surface.ts +0 -481
- package/src/setup-test-helpers.ts +0 -42
- package/src/shared.ts +0 -92
- package/src/status-issues.test.ts +0 -31
- package/src/status-issues.ts +0 -55
- package/src/test-helpers.ts +0 -26
- package/src/text-styles.test.ts +0 -203
- package/src/text-styles.ts +0 -540
- package/src/tool.test.ts +0 -212
- package/src/tool.ts +0 -200
- package/src/types.ts +0 -127
- package/src/zalo-js.credentials.test.ts +0 -465
- package/src/zalo-js.test-mocks.ts +0 -89
- package/src/zalo-js.ts +0 -1889
- package/src/zca-client.test.ts +0 -27
- package/src/zca-client.ts +0 -259
- package/src/zca-constants.ts +0 -55
- package/src/zca-js-exports.d.ts +0 -22
- package/test-api.ts +0 -21
- package/tsconfig.json +0 -16
package/src/text-styles.ts
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import { TextStyle, type Style } from "./zca-constants.js";
|
|
2
|
-
|
|
3
|
-
const ESCAPE_SENTINEL_START = "\u0001";
|
|
4
|
-
const ESCAPE_SENTINEL_END = "\u0002";
|
|
5
|
-
|
|
6
|
-
type InlineStyle = (typeof TextStyle)[keyof typeof TextStyle];
|
|
7
|
-
|
|
8
|
-
type LineStyle = {
|
|
9
|
-
lineIndex: number;
|
|
10
|
-
style: InlineStyle;
|
|
11
|
-
indentSize?: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
type Segment = {
|
|
15
|
-
text: string;
|
|
16
|
-
styles: InlineStyle[];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type InlineMarker = {
|
|
20
|
-
pattern: RegExp;
|
|
21
|
-
extractText: (match: RegExpExecArray) => string;
|
|
22
|
-
resolveStyles?: (match: RegExpExecArray) => InlineStyle[];
|
|
23
|
-
literal?: boolean;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type ResolvedInlineMatch = {
|
|
27
|
-
match: RegExpExecArray;
|
|
28
|
-
marker: InlineMarker;
|
|
29
|
-
styles: InlineStyle[];
|
|
30
|
-
text: string;
|
|
31
|
-
priority: number;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type FenceMarker = {
|
|
35
|
-
char: "`" | "~";
|
|
36
|
-
length: number;
|
|
37
|
-
indent: number;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type ActiveFence = FenceMarker & {
|
|
41
|
-
quoteIndent: number;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const TAG_STYLE_MAP: Record<string, InlineStyle | null> = {
|
|
45
|
-
red: TextStyle.Red,
|
|
46
|
-
orange: TextStyle.Orange,
|
|
47
|
-
yellow: TextStyle.Yellow,
|
|
48
|
-
green: TextStyle.Green,
|
|
49
|
-
small: null,
|
|
50
|
-
big: TextStyle.Big,
|
|
51
|
-
underline: TextStyle.Underline,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const INLINE_MARKERS: InlineMarker[] = [
|
|
55
|
-
{
|
|
56
|
-
pattern: /`([^`\n]+)`/g,
|
|
57
|
-
extractText: (match) => match[0],
|
|
58
|
-
literal: true,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
pattern: /\\([*_~#\\{}>+\-`])/g,
|
|
62
|
-
extractText: (match) => match[1],
|
|
63
|
-
literal: true,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
pattern: new RegExp(`\\{(${Object.keys(TAG_STYLE_MAP).join("|")})\\}(.+?)\\{/\\1\\}`, "g"),
|
|
67
|
-
extractText: (match) => match[2],
|
|
68
|
-
resolveStyles: (match) => {
|
|
69
|
-
const style = TAG_STYLE_MAP[match[1]];
|
|
70
|
-
return style ? [style] : [];
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
pattern: /(?<!\*)\*\*\*(?=\S)([^\n]*?\S)(?<!\*)\*\*\*(?!\*)/g,
|
|
75
|
-
extractText: (match) => match[1],
|
|
76
|
-
resolveStyles: () => [TextStyle.Bold, TextStyle.Italic],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
pattern: /(?<!\*)\*\*(?![\s*])([^\n]*?\S)(?<!\*)\*\*(?!\*)/g,
|
|
80
|
-
extractText: (match) => match[1],
|
|
81
|
-
resolveStyles: () => [TextStyle.Bold],
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
pattern: /(?<![\w_])__(?![\s_])([^\n]*?\S)(?<!_)__(?![\w_])/g,
|
|
85
|
-
extractText: (match) => match[1],
|
|
86
|
-
resolveStyles: () => [TextStyle.Bold],
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
pattern: /(?<!~)~~(?=\S)([^\n]*?\S)(?<!~)~~(?!~)/g,
|
|
90
|
-
extractText: (match) => match[1],
|
|
91
|
-
resolveStyles: () => [TextStyle.StrikeThrough],
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
pattern: /(?<!\*)\*(?![\s*])([^\n]*?\S)(?<!\*)\*(?!\*)/g,
|
|
95
|
-
extractText: (match) => match[1],
|
|
96
|
-
resolveStyles: () => [TextStyle.Italic],
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
pattern: /(?<![\w_])_(?![\s_])([^\n]*?\S)(?<!_)_(?![\w_])/g,
|
|
100
|
-
extractText: (match) => match[1],
|
|
101
|
-
resolveStyles: () => [TextStyle.Italic],
|
|
102
|
-
},
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
export function parseZalouserTextStyles(input: string): { text: string; styles: Style[] } {
|
|
106
|
-
const allStyles: Style[] = [];
|
|
107
|
-
|
|
108
|
-
const escapeMap: string[] = [];
|
|
109
|
-
const lines = input.replace(/\r\n?/g, "\n").split("\n");
|
|
110
|
-
const lineStyles: LineStyle[] = [];
|
|
111
|
-
const processedLines: string[] = [];
|
|
112
|
-
let activeFence: ActiveFence | null = null;
|
|
113
|
-
|
|
114
|
-
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
115
|
-
const rawLine = lines[lineIndex];
|
|
116
|
-
const { text: unquotedLine, indent: baseIndent } = stripQuotePrefix(rawLine);
|
|
117
|
-
|
|
118
|
-
if (activeFence) {
|
|
119
|
-
const codeLine =
|
|
120
|
-
activeFence.quoteIndent > 0
|
|
121
|
-
? stripQuotePrefix(rawLine, activeFence.quoteIndent).text
|
|
122
|
-
: rawLine;
|
|
123
|
-
if (isClosingFence(codeLine, activeFence)) {
|
|
124
|
-
activeFence = null;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
processedLines.push(
|
|
128
|
-
escapeLiteralText(
|
|
129
|
-
normalizeCodeBlockLeadingWhitespace(stripCodeFenceIndent(codeLine, activeFence.indent)),
|
|
130
|
-
escapeMap,
|
|
131
|
-
),
|
|
132
|
-
);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let line = unquotedLine;
|
|
137
|
-
const openingFence = resolveOpeningFence(rawLine);
|
|
138
|
-
if (openingFence) {
|
|
139
|
-
const fenceLine = openingFence.quoteIndent > 0 ? unquotedLine : rawLine;
|
|
140
|
-
if (!hasClosingFence(lines, lineIndex + 1, openingFence)) {
|
|
141
|
-
processedLines.push(escapeLiteralText(fenceLine, escapeMap));
|
|
142
|
-
activeFence = openingFence;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
activeFence = openingFence;
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const outputLineIndex = processedLines.length;
|
|
150
|
-
if (isIndentedCodeBlockLine(line)) {
|
|
151
|
-
if (baseIndent > 0) {
|
|
152
|
-
lineStyles.push({
|
|
153
|
-
lineIndex: outputLineIndex,
|
|
154
|
-
style: TextStyle.Indent,
|
|
155
|
-
indentSize: baseIndent,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
processedLines.push(escapeLiteralText(normalizeCodeBlockLeadingWhitespace(line), escapeMap));
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const { text: markdownLine, size: markdownPadding } = stripOptionalMarkdownPadding(line);
|
|
163
|
-
|
|
164
|
-
const headingMatch = markdownLine.match(/^(#{1,4})\s(.*)$/);
|
|
165
|
-
if (headingMatch) {
|
|
166
|
-
const depth = headingMatch[1].length;
|
|
167
|
-
lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.Bold });
|
|
168
|
-
if (depth === 1) {
|
|
169
|
-
lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.Big });
|
|
170
|
-
}
|
|
171
|
-
if (baseIndent > 0) {
|
|
172
|
-
lineStyles.push({
|
|
173
|
-
lineIndex: outputLineIndex,
|
|
174
|
-
style: TextStyle.Indent,
|
|
175
|
-
indentSize: baseIndent,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
processedLines.push(headingMatch[2]);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const indentMatch = markdownLine.match(/^(\s+)(.*)$/);
|
|
183
|
-
let indentLevel = 0;
|
|
184
|
-
let content = markdownLine;
|
|
185
|
-
if (indentMatch) {
|
|
186
|
-
indentLevel = clampIndent(indentMatch[1].length);
|
|
187
|
-
content = indentMatch[2];
|
|
188
|
-
}
|
|
189
|
-
const totalIndent = Math.min(5, baseIndent + indentLevel);
|
|
190
|
-
|
|
191
|
-
if (/^[-*+]\s\[[ xX]\]\s/.test(content)) {
|
|
192
|
-
if (totalIndent > 0) {
|
|
193
|
-
lineStyles.push({
|
|
194
|
-
lineIndex: outputLineIndex,
|
|
195
|
-
style: TextStyle.Indent,
|
|
196
|
-
indentSize: totalIndent,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
processedLines.push(content);
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const orderedListMatch = content.match(/^(\d+)\.\s(.*)$/);
|
|
204
|
-
if (orderedListMatch) {
|
|
205
|
-
if (totalIndent > 0) {
|
|
206
|
-
lineStyles.push({
|
|
207
|
-
lineIndex: outputLineIndex,
|
|
208
|
-
style: TextStyle.Indent,
|
|
209
|
-
indentSize: totalIndent,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.OrderedList });
|
|
213
|
-
processedLines.push(orderedListMatch[2]);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const unorderedListMatch = content.match(/^[-*+]\s(.*)$/);
|
|
218
|
-
if (unorderedListMatch) {
|
|
219
|
-
if (totalIndent > 0) {
|
|
220
|
-
lineStyles.push({
|
|
221
|
-
lineIndex: outputLineIndex,
|
|
222
|
-
style: TextStyle.Indent,
|
|
223
|
-
indentSize: totalIndent,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.UnorderedList });
|
|
227
|
-
processedLines.push(unorderedListMatch[1]);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (markdownPadding > 0) {
|
|
232
|
-
if (baseIndent > 0) {
|
|
233
|
-
lineStyles.push({
|
|
234
|
-
lineIndex: outputLineIndex,
|
|
235
|
-
style: TextStyle.Indent,
|
|
236
|
-
indentSize: baseIndent,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
processedLines.push(line);
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (totalIndent > 0) {
|
|
244
|
-
lineStyles.push({
|
|
245
|
-
lineIndex: outputLineIndex,
|
|
246
|
-
style: TextStyle.Indent,
|
|
247
|
-
indentSize: totalIndent,
|
|
248
|
-
});
|
|
249
|
-
processedLines.push(content);
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
processedLines.push(line);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const segments = parseInlineSegments(processedLines.join("\n"));
|
|
257
|
-
|
|
258
|
-
let plainText = "";
|
|
259
|
-
for (const segment of segments) {
|
|
260
|
-
const start = plainText.length;
|
|
261
|
-
plainText += segment.text;
|
|
262
|
-
for (const style of segment.styles) {
|
|
263
|
-
allStyles.push({ start, len: segment.text.length, st: style } as Style);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (escapeMap.length > 0) {
|
|
268
|
-
const escapeRegex = new RegExp(`${ESCAPE_SENTINEL_START}(\\d+)${ESCAPE_SENTINEL_END}`, "g");
|
|
269
|
-
const shifts: Array<{ pos: number; delta: number }> = [];
|
|
270
|
-
let cumulativeDelta = 0;
|
|
271
|
-
|
|
272
|
-
for (const match of plainText.matchAll(escapeRegex)) {
|
|
273
|
-
const escapeIndex = Number.parseInt(match[1], 10);
|
|
274
|
-
cumulativeDelta += match[0].length - escapeMap[escapeIndex].length;
|
|
275
|
-
shifts.push({ pos: (match.index ?? 0) + match[0].length, delta: cumulativeDelta });
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
for (const style of allStyles) {
|
|
279
|
-
let startDelta = 0;
|
|
280
|
-
let endDelta = 0;
|
|
281
|
-
const end = style.start + style.len;
|
|
282
|
-
for (const shift of shifts) {
|
|
283
|
-
if (shift.pos <= style.start) {
|
|
284
|
-
startDelta = shift.delta;
|
|
285
|
-
}
|
|
286
|
-
if (shift.pos <= end) {
|
|
287
|
-
endDelta = shift.delta;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
style.start -= startDelta;
|
|
291
|
-
style.len -= endDelta - startDelta;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
plainText = plainText.replace(
|
|
295
|
-
escapeRegex,
|
|
296
|
-
(_match, index) => escapeMap[Number.parseInt(index, 10)],
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const finalLines = plainText.split("\n");
|
|
301
|
-
let offset = 0;
|
|
302
|
-
for (let lineIndex = 0; lineIndex < finalLines.length; lineIndex += 1) {
|
|
303
|
-
const lineLength = finalLines[lineIndex].length;
|
|
304
|
-
if (lineLength > 0) {
|
|
305
|
-
for (const lineStyle of lineStyles) {
|
|
306
|
-
if (lineStyle.lineIndex !== lineIndex) {
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (lineStyle.style === TextStyle.Indent) {
|
|
311
|
-
allStyles.push({
|
|
312
|
-
start: offset,
|
|
313
|
-
len: lineLength,
|
|
314
|
-
st: TextStyle.Indent,
|
|
315
|
-
indentSize: lineStyle.indentSize,
|
|
316
|
-
});
|
|
317
|
-
} else {
|
|
318
|
-
allStyles.push({ start: offset, len: lineLength, st: lineStyle.style } as Style);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
offset += lineLength + 1;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return { text: plainText, styles: allStyles };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function clampIndent(spaceCount: number): number {
|
|
329
|
-
return Math.min(5, Math.max(1, Math.floor(spaceCount / 2)));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function stripOptionalMarkdownPadding(line: string): { text: string; size: number } {
|
|
333
|
-
const match = line.match(/^( {1,3})(?=\S)/);
|
|
334
|
-
if (!match) {
|
|
335
|
-
return { text: line, size: 0 };
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
text: line.slice(match[1].length),
|
|
339
|
-
size: match[1].length,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function hasClosingFence(lines: string[], startIndex: number, fence: ActiveFence): boolean {
|
|
344
|
-
for (let index = startIndex; index < lines.length; index += 1) {
|
|
345
|
-
const candidate =
|
|
346
|
-
fence.quoteIndent > 0 ? stripQuotePrefix(lines[index], fence.quoteIndent).text : lines[index];
|
|
347
|
-
if (isClosingFence(candidate, fence)) {
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function resolveOpeningFence(line: string): ActiveFence | null {
|
|
355
|
-
const directFence = parseFenceMarker(line);
|
|
356
|
-
if (directFence) {
|
|
357
|
-
return { ...directFence, quoteIndent: 0 };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const quoted = stripQuotePrefix(line);
|
|
361
|
-
if (quoted.indent === 0) {
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const quotedFence = parseFenceMarker(quoted.text);
|
|
366
|
-
if (!quotedFence) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return {
|
|
371
|
-
...quotedFence,
|
|
372
|
-
quoteIndent: quoted.indent,
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function stripQuotePrefix(
|
|
377
|
-
line: string,
|
|
378
|
-
maxDepth = Number.POSITIVE_INFINITY,
|
|
379
|
-
): { text: string; indent: number } {
|
|
380
|
-
let cursor = 0;
|
|
381
|
-
while (cursor < line.length && cursor < 3 && line[cursor] === " ") {
|
|
382
|
-
cursor += 1;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
let removedDepth = 0;
|
|
386
|
-
let consumedCursor = cursor;
|
|
387
|
-
while (removedDepth < maxDepth && consumedCursor < line.length && line[consumedCursor] === ">") {
|
|
388
|
-
removedDepth += 1;
|
|
389
|
-
consumedCursor += 1;
|
|
390
|
-
if (line[consumedCursor] === " ") {
|
|
391
|
-
consumedCursor += 1;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (removedDepth === 0) {
|
|
396
|
-
return { text: line, indent: 0 };
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
text: line.slice(consumedCursor),
|
|
401
|
-
indent: Math.min(5, removedDepth),
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function parseFenceMarker(line: string): FenceMarker | null {
|
|
406
|
-
const match = line.match(/^([ ]{0,3})(`{3,}|~{3,})(.*)$/);
|
|
407
|
-
if (!match) {
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const marker = match[2];
|
|
412
|
-
const char = marker[0];
|
|
413
|
-
if (char !== "`" && char !== "~") {
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return {
|
|
418
|
-
char,
|
|
419
|
-
length: marker.length,
|
|
420
|
-
indent: match[1].length,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function isClosingFence(line: string, fence: FenceMarker): boolean {
|
|
425
|
-
const match = line.match(/^([ ]{0,3})(`{3,}|~{3,})[ \t]*$/);
|
|
426
|
-
if (!match) {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
return match[2][0] === fence.char && match[2].length >= fence.length;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function escapeLiteralText(input: string, escapeMap: string[]): string {
|
|
433
|
-
return input.replace(/[\\*_~{}`]/g, (ch) => {
|
|
434
|
-
const index = escapeMap.length;
|
|
435
|
-
escapeMap.push(ch);
|
|
436
|
-
return `\x01${index}\x02`;
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function parseInlineSegments(text: string, inheritedStyles: InlineStyle[] = []): Segment[] {
|
|
441
|
-
const segments: Segment[] = [];
|
|
442
|
-
let cursor = 0;
|
|
443
|
-
|
|
444
|
-
while (cursor < text.length) {
|
|
445
|
-
const nextMatch = findNextInlineMatch(text, cursor);
|
|
446
|
-
if (!nextMatch) {
|
|
447
|
-
pushSegment(segments, text.slice(cursor), inheritedStyles);
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (nextMatch.match.index > cursor) {
|
|
452
|
-
pushSegment(segments, text.slice(cursor, nextMatch.match.index), inheritedStyles);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const combinedStyles = [...inheritedStyles, ...nextMatch.styles];
|
|
456
|
-
if (nextMatch.marker.literal) {
|
|
457
|
-
pushSegment(segments, nextMatch.text, combinedStyles);
|
|
458
|
-
} else {
|
|
459
|
-
segments.push(...parseInlineSegments(nextMatch.text, combinedStyles));
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
cursor = nextMatch.match.index + nextMatch.match[0].length;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return segments;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function findNextInlineMatch(text: string, startIndex: number): ResolvedInlineMatch | null {
|
|
469
|
-
let bestMatch: ResolvedInlineMatch | null = null;
|
|
470
|
-
|
|
471
|
-
for (const [priority, marker] of INLINE_MARKERS.entries()) {
|
|
472
|
-
const regex = new RegExp(marker.pattern.source, marker.pattern.flags);
|
|
473
|
-
regex.lastIndex = startIndex;
|
|
474
|
-
const match = regex.exec(text);
|
|
475
|
-
if (!match) {
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (
|
|
480
|
-
bestMatch &&
|
|
481
|
-
(match.index > bestMatch.match.index ||
|
|
482
|
-
(match.index === bestMatch.match.index && priority > bestMatch.priority))
|
|
483
|
-
) {
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
bestMatch = {
|
|
488
|
-
match,
|
|
489
|
-
marker,
|
|
490
|
-
text: marker.extractText(match),
|
|
491
|
-
styles: marker.resolveStyles?.(match) ?? [],
|
|
492
|
-
priority,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return bestMatch;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function pushSegment(segments: Segment[], text: string, styles: InlineStyle[]): void {
|
|
500
|
-
if (!text) {
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const lastSegment = segments.at(-1);
|
|
505
|
-
if (lastSegment && sameStyles(lastSegment.styles, styles)) {
|
|
506
|
-
lastSegment.text += text;
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
segments.push({
|
|
511
|
-
text,
|
|
512
|
-
styles: [...styles],
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function sameStyles(left: InlineStyle[], right: InlineStyle[]): boolean {
|
|
517
|
-
return left.length === right.length && left.every((style, index) => style === right[index]);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function normalizeCodeBlockLeadingWhitespace(line: string): string {
|
|
521
|
-
return line.replace(/^[ \t]+/, (leadingWhitespace) =>
|
|
522
|
-
leadingWhitespace.replace(/\t/g, "\u00A0\u00A0\u00A0\u00A0").replace(/ /g, "\u00A0"),
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function isIndentedCodeBlockLine(line: string): boolean {
|
|
527
|
-
return /^(?: {4,}|\t)/.test(line);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function stripCodeFenceIndent(line: string, indent: number): string {
|
|
531
|
-
let consumed = 0;
|
|
532
|
-
let cursor = 0;
|
|
533
|
-
|
|
534
|
-
while (cursor < line.length && consumed < indent && line[cursor] === " ") {
|
|
535
|
-
cursor += 1;
|
|
536
|
-
consumed += 1;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return line.slice(cursor);
|
|
540
|
-
}
|