@urbicon-ui/blocks 6.35.0 → 6.37.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/components/Chat/Chat/Chat.svelte +53 -0
- package/dist/components/Chat/Chat/Chat.svelte.d.ts +4 -0
- package/dist/components/Chat/Chat/chat.variants.d.ts +20 -0
- package/dist/components/Chat/Chat/chat.variants.js +15 -0
- package/dist/components/Chat/Chat/index.d.ts +47 -0
- package/dist/components/Chat/Chat/index.js +2 -0
- package/dist/components/Chat/ChatMessage/ChatMessage.svelte +342 -0
- package/dist/components/Chat/ChatMessage/ChatMessage.svelte.d.ts +4 -0
- package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +138 -0
- package/dist/components/Chat/ChatMessage/chat-message.variants.js +118 -0
- package/dist/components/Chat/ChatMessage/index.d.ts +102 -0
- package/dist/components/Chat/ChatMessage/index.js +2 -0
- package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte +301 -0
- package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte.d.ts +6 -0
- package/dist/components/Chat/ChatMessageList/chat-message-list.variants.d.ts +23 -0
- package/dist/components/Chat/ChatMessageList/chat-message-list.variants.js +32 -0
- package/dist/components/Chat/ChatMessageList/chat-scroll.d.ts +45 -0
- package/dist/components/Chat/ChatMessageList/chat-scroll.js +52 -0
- package/dist/components/Chat/ChatMessageList/index.d.ts +93 -0
- package/dist/components/Chat/ChatMessageList/index.js +3 -0
- package/dist/components/Chat/CitationChip/CitationChip.svelte +107 -0
- package/dist/components/Chat/CitationChip/CitationChip.svelte.d.ts +4 -0
- package/dist/components/Chat/CitationChip/citation-chip.variants.d.ts +40 -0
- package/dist/components/Chat/CitationChip/citation-chip.variants.js +42 -0
- package/dist/components/Chat/CitationChip/index.d.ts +69 -0
- package/dist/components/Chat/CitationChip/index.js +2 -0
- package/dist/components/Chat/CodeBlock/CodeBlock.svelte +120 -0
- package/dist/components/Chat/CodeBlock/CodeBlock.svelte.d.ts +4 -0
- package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +40 -0
- package/dist/components/Chat/CodeBlock/code-block.variants.js +51 -0
- package/dist/components/Chat/CodeBlock/index.d.ts +55 -0
- package/dist/components/Chat/CodeBlock/index.js +2 -0
- package/dist/components/Chat/PromptInput/PromptInput.svelte +432 -0
- package/dist/components/Chat/PromptInput/PromptInput.svelte.d.ts +4 -0
- package/dist/components/Chat/PromptInput/index.d.ts +165 -0
- package/dist/components/Chat/PromptInput/index.js +2 -0
- package/dist/components/Chat/PromptInput/prompt-input.variants.d.ts +90 -0
- package/dist/components/Chat/PromptInput/prompt-input.variants.js +102 -0
- package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte +107 -0
- package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte.d.ts +4 -0
- package/dist/components/Chat/ReasoningDisclosure/index.d.ts +85 -0
- package/dist/components/Chat/ReasoningDisclosure/index.js +2 -0
- package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.d.ts +20 -0
- package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.js +31 -0
- package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte +99 -0
- package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte.d.ts +9 -0
- package/dist/components/Chat/StreamingMarkdown/MdInline.svelte +82 -0
- package/dist/components/Chat/StreamingMarkdown/MdInline.svelte.d.ts +9 -0
- package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte +118 -0
- package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte.d.ts +4 -0
- package/dist/components/Chat/StreamingMarkdown/index.d.ts +86 -0
- package/dist/components/Chat/StreamingMarkdown/index.js +2 -0
- package/dist/components/Chat/StreamingMarkdown/md-context.d.ts +58 -0
- package/dist/components/Chat/StreamingMarkdown/md-context.js +1 -0
- package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.d.ts +145 -0
- package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.js +73 -0
- package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +174 -0
- package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte.d.ts +4 -0
- package/dist/components/Chat/ToolCallCard/index.d.ts +84 -0
- package/dist/components/Chat/ToolCallCard/index.js +2 -0
- package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +38 -0
- package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +38 -0
- package/dist/components/Chat/chat.types.d.ts +82 -0
- package/dist/components/Chat/chat.types.js +7 -0
- package/dist/components/Chat/index.d.ts +22 -0
- package/dist/components/Chat/index.js +14 -0
- package/dist/components/Chat/markdown/blocks.d.ts +54 -0
- package/dist/components/Chat/markdown/blocks.js +462 -0
- package/dist/components/Chat/markdown/inline.d.ts +31 -0
- package/dist/components/Chat/markdown/inline.js +446 -0
- package/dist/components/Chat/markdown/repair.d.ts +23 -0
- package/dist/components/Chat/markdown/repair.js +337 -0
- package/dist/components/Chat/markdown/streaming-harness.d.ts +23 -0
- package/dist/components/Chat/markdown/streaming-harness.js +62 -0
- package/dist/components/Chat/markdown/types.d.ts +139 -0
- package/dist/components/Chat/markdown/types.js +13 -0
- package/dist/components/Chat/markdown/url-policy.d.ts +33 -0
- package/dist/components/Chat/markdown/url-policy.js +109 -0
- package/dist/components/FileUpload/FileUpload.svelte +35 -123
- package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
- package/dist/components/FileUpload/index.d.ts +2 -28
- package/dist/components/PinInput/PinInput.svelte +321 -0
- package/dist/components/PinInput/PinInput.svelte.d.ts +4 -0
- package/dist/components/PinInput/index.d.ts +98 -0
- package/dist/components/PinInput/index.js +2 -0
- package/dist/components/PinInput/pin-input.variants.d.ts +96 -0
- package/dist/components/PinInput/pin-input.variants.js +111 -0
- package/dist/components/QRCode/QRCode.svelte +107 -0
- package/dist/components/QRCode/QRCode.svelte.d.ts +4 -0
- package/dist/components/QRCode/index.d.ts +74 -0
- package/dist/components/QRCode/index.js +3 -0
- package/dist/components/QRCode/qr-code.variants.d.ts +25 -0
- package/dist/components/QRCode/qr-code.variants.js +24 -0
- package/dist/components/QRCode/qr-encode.d.ts +22 -0
- package/dist/components/QRCode/qr-encode.js +528 -0
- package/dist/components/TimeInput/TimeInput.svelte +521 -0
- package/dist/components/TimeInput/TimeInput.svelte.d.ts +4 -0
- package/dist/components/TimeInput/index.d.ts +93 -0
- package/dist/components/TimeInput/index.js +2 -0
- package/dist/components/TimeInput/time-input.variants.d.ts +131 -0
- package/dist/components/TimeInput/time-input.variants.js +120 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.js +4 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
- package/dist/primitives/SplitPane/SplitPane.svelte +293 -0
- package/dist/primitives/SplitPane/SplitPane.svelte.d.ts +4 -0
- package/dist/primitives/SplitPane/index.d.ts +88 -0
- package/dist/primitives/SplitPane/index.js +2 -0
- package/dist/primitives/SplitPane/split-pane.utils.d.ts +73 -0
- package/dist/primitives/SplitPane/split-pane.utils.js +94 -0
- package/dist/primitives/SplitPane/split-pane.variants.d.ts +40 -0
- package/dist/primitives/SplitPane/split-pane.variants.js +56 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/translations/de.d.ts +6 -0
- package/dist/translations/de.js +6 -0
- package/dist/translations/en.d.ts +6 -0
- package/dist/translations/en.js +6 -0
- package/dist/utils/file-intake.d.ts +78 -0
- package/dist/utils/file-intake.js +144 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { checkImageUrl, checkLinkUrl } from './url-policy.js';
|
|
2
|
+
const PUNCT_BANG = '!';
|
|
3
|
+
function isWordChar(ch) {
|
|
4
|
+
if (ch === undefined)
|
|
5
|
+
return false;
|
|
6
|
+
return /[\p{L}\p{N}_]/u.test(ch);
|
|
7
|
+
}
|
|
8
|
+
function isSpaceLike(ch) {
|
|
9
|
+
return ch === undefined || /\s/.test(ch);
|
|
10
|
+
}
|
|
11
|
+
export function parseInlines(input, ctx) {
|
|
12
|
+
return matchDelimiters(tokenize(input, ctx, false));
|
|
13
|
+
}
|
|
14
|
+
function tokenize(input, ctx, insideLink) {
|
|
15
|
+
const tokens = [];
|
|
16
|
+
let text = '';
|
|
17
|
+
const flush = () => {
|
|
18
|
+
if (text.length > 0) {
|
|
19
|
+
tokens.push({ t: 'text', text });
|
|
20
|
+
text = '';
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < input.length) {
|
|
25
|
+
const ch = input[i];
|
|
26
|
+
// Backslash escapes — the escaped char becomes literal text.
|
|
27
|
+
if (ch === '\\' && i + 1 < input.length) {
|
|
28
|
+
const next = input[i + 1];
|
|
29
|
+
if (next === '\n') {
|
|
30
|
+
flush();
|
|
31
|
+
tokens.push({ t: 'node', node: { kind: 'break' } });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
text += next;
|
|
35
|
+
}
|
|
36
|
+
i += 2;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// Hard break: two+ spaces before newline. Soft break renders as a space.
|
|
40
|
+
if (ch === '\n') {
|
|
41
|
+
const hard = /[ ]{2,}$/.test(text);
|
|
42
|
+
text = text.replace(/[ \t]+$/, '');
|
|
43
|
+
flush();
|
|
44
|
+
if (hard)
|
|
45
|
+
tokens.push({ t: 'node', node: { kind: 'break' } });
|
|
46
|
+
else
|
|
47
|
+
text += ' ';
|
|
48
|
+
i += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// Code spans: a backtick run closed by an equally long run.
|
|
52
|
+
if (ch === '`') {
|
|
53
|
+
let runLen = 1;
|
|
54
|
+
while (input[i + runLen] === '`')
|
|
55
|
+
runLen += 1;
|
|
56
|
+
const close = findCodeSpanClose(input, i + runLen, runLen);
|
|
57
|
+
if (close !== -1) {
|
|
58
|
+
flush();
|
|
59
|
+
let code = input.slice(i + runLen, close).replace(/\n/g, ' ');
|
|
60
|
+
if (code.length >= 2 && code.startsWith(' ') && code.endsWith(' ') && code.trim() !== '') {
|
|
61
|
+
code = code.slice(1, -1);
|
|
62
|
+
}
|
|
63
|
+
tokens.push({ t: 'node', node: { kind: 'code', text: code } });
|
|
64
|
+
i = close + runLen;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
text += input.slice(i, i + runLen);
|
|
68
|
+
i += runLen;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Images / links / citations / reference links.
|
|
73
|
+
const isImage = ch === PUNCT_BANG && input[i + 1] === '[';
|
|
74
|
+
if ((ch === '[' && !insideLink) || isImage) {
|
|
75
|
+
const parsed = parseBracketConstruct(input, i, isImage, ctx);
|
|
76
|
+
if (parsed) {
|
|
77
|
+
flush();
|
|
78
|
+
tokens.push({ t: 'node', node: parsed.node });
|
|
79
|
+
i = parsed.end;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Fullwidth citation markers 【id】 (some models emit these).
|
|
84
|
+
if (ch === '【') {
|
|
85
|
+
const parsed = tryFullwidthCitation(input, i, ctx);
|
|
86
|
+
if (parsed) {
|
|
87
|
+
flush();
|
|
88
|
+
tokens.push({ t: 'node', node: parsed.node });
|
|
89
|
+
i = parsed.end;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Emphasis / strike delimiter runs.
|
|
94
|
+
if (ch === '*' || ch === '_' || ch === '~') {
|
|
95
|
+
let runLen = 1;
|
|
96
|
+
while (input[i + runLen] === ch)
|
|
97
|
+
runLen += 1;
|
|
98
|
+
const before = i > 0 ? input[i - 1] : undefined;
|
|
99
|
+
const after = input[i + runLen];
|
|
100
|
+
let canOpen = !isSpaceLike(after);
|
|
101
|
+
let canClose = !isSpaceLike(before);
|
|
102
|
+
if (ch === '_') {
|
|
103
|
+
// No intraword emphasis for underscores (snake_case stays intact).
|
|
104
|
+
if (isWordChar(before) && isWordChar(after)) {
|
|
105
|
+
canOpen = false;
|
|
106
|
+
canClose = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (ch === '~' && runLen < 2) {
|
|
110
|
+
// Single tilde is plain text (20~25°C).
|
|
111
|
+
text += input.slice(i, i + runLen);
|
|
112
|
+
i += runLen;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (canOpen || canClose) {
|
|
116
|
+
flush();
|
|
117
|
+
tokens.push({ t: 'delim', marker: ch, length: runLen, canOpen, canClose });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
text += input.slice(i, i + runLen);
|
|
121
|
+
}
|
|
122
|
+
i += runLen;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// GFM-style autolinks (opt-in).
|
|
126
|
+
if (ctx.options.autolink && (ch === 'h' || ch === 'H')) {
|
|
127
|
+
const rest = input.slice(i);
|
|
128
|
+
const match = /^https?:\/\/[^\s<>]+/i.exec(rest);
|
|
129
|
+
if (match && !isWordChar(input[i - 1])) {
|
|
130
|
+
const url = match[0].replace(/[.,;:!?)\]]+$/, '');
|
|
131
|
+
flush();
|
|
132
|
+
tokens.push({
|
|
133
|
+
t: 'node',
|
|
134
|
+
node: makeLink(url, undefined, [{ kind: 'text', text: url }], ctx)
|
|
135
|
+
});
|
|
136
|
+
i += url.length;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
text += ch;
|
|
141
|
+
i += 1;
|
|
142
|
+
}
|
|
143
|
+
flush();
|
|
144
|
+
return tokens;
|
|
145
|
+
}
|
|
146
|
+
function findCodeSpanClose(input, from, runLen) {
|
|
147
|
+
let i = from;
|
|
148
|
+
while (i < input.length) {
|
|
149
|
+
if (input[i] === '`') {
|
|
150
|
+
let len = 1;
|
|
151
|
+
while (input[i + len] === '`')
|
|
152
|
+
len += 1;
|
|
153
|
+
if (len === runLen)
|
|
154
|
+
return i;
|
|
155
|
+
i += len;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
i += 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return -1;
|
|
162
|
+
}
|
|
163
|
+
// ── Bracket constructs: links, images, references, citations ────────────────
|
|
164
|
+
const CITATION_FULLWIDTH = /^【([^】\n]{1,64})】/;
|
|
165
|
+
function parseBracketConstruct(input, start, isImage, ctx) {
|
|
166
|
+
const openBracket = isImage ? start + 1 : start;
|
|
167
|
+
const closeBracket = findBracketClose(input, openBracket);
|
|
168
|
+
if (closeBracket === -1)
|
|
169
|
+
return null;
|
|
170
|
+
const label = input.slice(openBracket + 1, closeBracket);
|
|
171
|
+
const afterLabel = closeBracket + 1;
|
|
172
|
+
// Inline destination: [label](dest "title")
|
|
173
|
+
if (input[afterLabel] === '(') {
|
|
174
|
+
const dest = parseDestination(input, afterLabel + 1);
|
|
175
|
+
if (dest) {
|
|
176
|
+
const node = isImage
|
|
177
|
+
? makeImage(dest.href, label, dest.title, ctx)
|
|
178
|
+
: makeLink(dest.href, dest.title, parseLinkText(label, ctx), ctx);
|
|
179
|
+
return { node, end: dest.end };
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
// Reference: [label][ref] — empty ref falls back to the label.
|
|
184
|
+
if (input[afterLabel] === '[') {
|
|
185
|
+
const refClose = input.indexOf(']', afterLabel + 1);
|
|
186
|
+
if (refClose !== -1) {
|
|
187
|
+
const refLabel = (input.slice(afterLabel + 1, refClose) || label).trim().toLowerCase();
|
|
188
|
+
const def = ctx.linkRefs.get(refLabel);
|
|
189
|
+
if (def) {
|
|
190
|
+
const node = isImage
|
|
191
|
+
? makeImage(def.href, label, def.title, ctx)
|
|
192
|
+
: makeLink(def.href, def.title, parseLinkText(label, ctx), ctx);
|
|
193
|
+
return { node, end: refClose + 1 };
|
|
194
|
+
}
|
|
195
|
+
ctx.onUnresolvedRef?.(refLabel);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!isImage) {
|
|
200
|
+
// Citation marker [id] — only for known ids, so prose like [1] survives.
|
|
201
|
+
if (ctx.options.citationIds?.has(label)) {
|
|
202
|
+
return { node: { kind: 'citation', id: label }, end: afterLabel };
|
|
203
|
+
}
|
|
204
|
+
// Shortcut reference [label].
|
|
205
|
+
const shortcutLabel = label.trim().toLowerCase();
|
|
206
|
+
const def = ctx.linkRefs.get(shortcutLabel);
|
|
207
|
+
if (def) {
|
|
208
|
+
return {
|
|
209
|
+
node: makeLink(def.href, def.title, parseLinkText(label, ctx), ctx),
|
|
210
|
+
end: afterLabel
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (shortcutLabel.length > 0)
|
|
214
|
+
ctx.onUnresolvedRef?.(shortcutLabel);
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
/** Parse citation markers of the form 【id】 (some models emit fullwidth brackets). */
|
|
219
|
+
export function tryFullwidthCitation(input, at, ctx) {
|
|
220
|
+
const match = CITATION_FULLWIDTH.exec(input.slice(at));
|
|
221
|
+
if (match && ctx.options.citationIds?.has(match[1])) {
|
|
222
|
+
return { node: { kind: 'citation', id: match[1] }, end: at + match[0].length };
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
function findBracketClose(input, open) {
|
|
227
|
+
let depth = 0;
|
|
228
|
+
for (let i = open; i < input.length; i += 1) {
|
|
229
|
+
const ch = input[i];
|
|
230
|
+
if (ch === '\\') {
|
|
231
|
+
i += 1;
|
|
232
|
+
}
|
|
233
|
+
else if (ch === '[') {
|
|
234
|
+
depth += 1;
|
|
235
|
+
}
|
|
236
|
+
else if (ch === ']') {
|
|
237
|
+
depth -= 1;
|
|
238
|
+
if (depth === 0)
|
|
239
|
+
return i;
|
|
240
|
+
}
|
|
241
|
+
else if (ch === '\n' && input[i + 1] === '\n') {
|
|
242
|
+
return -1;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return -1;
|
|
246
|
+
}
|
|
247
|
+
function parseDestination(input, from) {
|
|
248
|
+
let i = from;
|
|
249
|
+
while (i < input.length && /[ \t\n]/.test(input[i]))
|
|
250
|
+
i += 1;
|
|
251
|
+
let href = '';
|
|
252
|
+
let depth = 0;
|
|
253
|
+
if (input[i] === '<') {
|
|
254
|
+
const close = input.indexOf('>', i + 1);
|
|
255
|
+
if (close === -1)
|
|
256
|
+
return null;
|
|
257
|
+
href = input.slice(i + 1, close);
|
|
258
|
+
i = close + 1;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
while (i < input.length) {
|
|
262
|
+
const ch = input[i];
|
|
263
|
+
if (ch === '\\' && i + 1 < input.length) {
|
|
264
|
+
href += input[i + 1];
|
|
265
|
+
i += 2;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (/[\s]/.test(ch))
|
|
269
|
+
break;
|
|
270
|
+
if (ch === '(')
|
|
271
|
+
depth += 1;
|
|
272
|
+
if (ch === ')') {
|
|
273
|
+
if (depth === 0)
|
|
274
|
+
break;
|
|
275
|
+
depth -= 1;
|
|
276
|
+
}
|
|
277
|
+
href += ch;
|
|
278
|
+
i += 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
while (i < input.length && /[ \t\n]/.test(input[i]))
|
|
282
|
+
i += 1;
|
|
283
|
+
let title;
|
|
284
|
+
const quote = input[i];
|
|
285
|
+
if (quote === '"' || quote === "'") {
|
|
286
|
+
const close = input.indexOf(quote, i + 1);
|
|
287
|
+
if (close === -1)
|
|
288
|
+
return null;
|
|
289
|
+
title = input.slice(i + 1, close);
|
|
290
|
+
i = close + 1;
|
|
291
|
+
while (i < input.length && /[ \t\n]/.test(input[i]))
|
|
292
|
+
i += 1;
|
|
293
|
+
}
|
|
294
|
+
if (input[i] !== ')')
|
|
295
|
+
return null;
|
|
296
|
+
return { href, title, end: i + 1 };
|
|
297
|
+
}
|
|
298
|
+
/** Link text is parsed without nested links (CommonMark rule). */
|
|
299
|
+
function parseLinkText(label, ctx) {
|
|
300
|
+
return matchDelimiters(tokenize(label, ctx, true));
|
|
301
|
+
}
|
|
302
|
+
function makeLink(href, title, children, ctx) {
|
|
303
|
+
const check = checkLinkUrl(href, ctx.options.urlPolicy);
|
|
304
|
+
if (check.ok)
|
|
305
|
+
return { kind: 'link', href: check.href, title, children };
|
|
306
|
+
return { kind: 'link', href: '', title, children, blocked: true };
|
|
307
|
+
}
|
|
308
|
+
function makeImage(src, alt, title, ctx) {
|
|
309
|
+
const check = checkImageUrl(src, ctx.options.urlPolicy);
|
|
310
|
+
if (check.ok)
|
|
311
|
+
return { kind: 'image', src: check.href, alt, title };
|
|
312
|
+
return { kind: 'image', src: '', alt, title, blocked: true };
|
|
313
|
+
}
|
|
314
|
+
function matchDelimiters(tokens) {
|
|
315
|
+
// Resolve delimiter pairs into explicit open/close instructions, innermost
|
|
316
|
+
// first, then build the tree in a single walk.
|
|
317
|
+
const opens = [];
|
|
318
|
+
const spans = [];
|
|
319
|
+
const consumedOpen = new Map();
|
|
320
|
+
const consumedClose = new Map();
|
|
321
|
+
tokens.forEach((token, index) => {
|
|
322
|
+
if (token.t !== 'delim')
|
|
323
|
+
return;
|
|
324
|
+
let remainingClose = token.length;
|
|
325
|
+
if (token.canClose) {
|
|
326
|
+
while (remainingClose > 0) {
|
|
327
|
+
const openerIdx = findOpener(opens, token.marker);
|
|
328
|
+
if (openerIdx === -1)
|
|
329
|
+
break;
|
|
330
|
+
const opener = opens[openerIdx];
|
|
331
|
+
const isStrike = token.marker === '~';
|
|
332
|
+
const take = isStrike ? 2 : Math.min(2, opener.remaining, remainingClose);
|
|
333
|
+
if (isStrike && (opener.remaining < 2 || remainingClose < 2))
|
|
334
|
+
break;
|
|
335
|
+
spans.push({
|
|
336
|
+
openToken: opener.tokenIndex,
|
|
337
|
+
closeToken: index,
|
|
338
|
+
openLen: take,
|
|
339
|
+
kind: isStrike ? 'strike' : take === 2 ? 'strong' : 'em'
|
|
340
|
+
});
|
|
341
|
+
opener.remaining -= take;
|
|
342
|
+
remainingClose -= take;
|
|
343
|
+
consumedOpen.set(opener.tokenIndex, (consumedOpen.get(opener.tokenIndex) ?? 0) + take);
|
|
344
|
+
consumedClose.set(index, (consumedClose.get(index) ?? 0) + take);
|
|
345
|
+
// Discard younger openers of other markers (CommonMark rule) — this
|
|
346
|
+
// keeps resolved spans strictly nested, which the tree walk relies on.
|
|
347
|
+
opens.splice(openerIdx + 1);
|
|
348
|
+
if (opener.remaining === 0)
|
|
349
|
+
opens.splice(openerIdx, 1);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (token.canOpen && remainingClose > 0) {
|
|
353
|
+
opens.push({ tokenIndex: index, marker: token.marker, remaining: remainingClose });
|
|
354
|
+
}
|
|
355
|
+
else if (remainingClose > 0) {
|
|
356
|
+
// Unmatched closer with no opening ability → renders literally later.
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
// Build tree: walk tokens, entering/leaving spans at their token indices.
|
|
360
|
+
// Spans are properly nested by construction (stack discipline above).
|
|
361
|
+
const byOpen = new Map();
|
|
362
|
+
const byClose = new Map();
|
|
363
|
+
for (const span of spans) {
|
|
364
|
+
push(byOpen, span.openToken, span);
|
|
365
|
+
push(byClose, span.closeToken, span);
|
|
366
|
+
}
|
|
367
|
+
const root = [];
|
|
368
|
+
const stack = [];
|
|
369
|
+
const sink = () => (stack.length > 0 ? stack[stack.length - 1].children : root);
|
|
370
|
+
tokens.forEach((token, index) => {
|
|
371
|
+
if (token.t === 'delim') {
|
|
372
|
+
const closing = (byClose.get(index) ?? []).sort((a, b) => b.openToken - a.openToken);
|
|
373
|
+
for (const span of closing) {
|
|
374
|
+
while (stack.length > 0) {
|
|
375
|
+
const top = stack.pop();
|
|
376
|
+
if (!top)
|
|
377
|
+
break;
|
|
378
|
+
const node = top.span.kind === 'strong'
|
|
379
|
+
? { kind: 'strong', children: top.children }
|
|
380
|
+
: top.span.kind === 'em'
|
|
381
|
+
? { kind: 'em', children: top.children }
|
|
382
|
+
: { kind: 'strike', children: top.children };
|
|
383
|
+
sink().push(node);
|
|
384
|
+
if (top.span === span)
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const literalLen = token.length - (consumedOpen.get(index) ?? 0) - (consumedClose.get(index) ?? 0);
|
|
389
|
+
if (literalLen > 0) {
|
|
390
|
+
appendText(sink(), token.marker.repeat(literalLen));
|
|
391
|
+
}
|
|
392
|
+
const opening = (byOpen.get(index) ?? []).sort((a, b) => b.closeToken - a.closeToken);
|
|
393
|
+
for (const span of opening) {
|
|
394
|
+
stack.push({ span, children: [] });
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (token.t === 'text') {
|
|
399
|
+
appendText(sink(), token.text);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
sink().push(token.node);
|
|
403
|
+
});
|
|
404
|
+
// Unclosed spans render literally: unwind, re-inserting the literal marker.
|
|
405
|
+
while (stack.length > 0) {
|
|
406
|
+
const top = stack.pop();
|
|
407
|
+
if (!top)
|
|
408
|
+
break;
|
|
409
|
+
const marker = top.span.kind === 'strike' ? '~~' : top.span.kind === 'strong' ? '**' : '*';
|
|
410
|
+
const target = sink();
|
|
411
|
+
appendText(target, marker);
|
|
412
|
+
for (const child of top.children) {
|
|
413
|
+
if (child.kind === 'text')
|
|
414
|
+
appendText(target, child.text);
|
|
415
|
+
else
|
|
416
|
+
target.push(child);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return mergeText(root);
|
|
420
|
+
}
|
|
421
|
+
function findOpener(opens, marker) {
|
|
422
|
+
for (let i = opens.length - 1; i >= 0; i -= 1) {
|
|
423
|
+
if (opens[i].marker === marker)
|
|
424
|
+
return i;
|
|
425
|
+
}
|
|
426
|
+
return -1;
|
|
427
|
+
}
|
|
428
|
+
function push(map, key, value) {
|
|
429
|
+
const list = map.get(key);
|
|
430
|
+
if (list)
|
|
431
|
+
list.push(value);
|
|
432
|
+
else
|
|
433
|
+
map.set(key, [value]);
|
|
434
|
+
}
|
|
435
|
+
function appendText(target, text) {
|
|
436
|
+
if (text.length === 0)
|
|
437
|
+
return;
|
|
438
|
+
const last = target[target.length - 1];
|
|
439
|
+
if (last && last.kind === 'text')
|
|
440
|
+
last.text += text;
|
|
441
|
+
else
|
|
442
|
+
target.push({ kind: 'text', text });
|
|
443
|
+
}
|
|
444
|
+
function mergeText(nodes) {
|
|
445
|
+
return nodes;
|
|
446
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming tail repair — the "remend" step for the incremental markdown engine.
|
|
3
|
+
*
|
|
4
|
+
* The incremental parser calls {@link repairMarkdownTail} on the *unsettled tail*
|
|
5
|
+
* of the stream before parsing it, so half-arrived syntax renders as its settled
|
|
6
|
+
* form instead of flickering (a dangling `**bold` shows as bold immediately, not
|
|
7
|
+
* as two literal asterisks that vanish on the next chunk).
|
|
8
|
+
*
|
|
9
|
+
* Hard invariant: on text that already ends in complete syntax the function is an
|
|
10
|
+
* exact no-op (returns the input unchanged). That is what keeps the streamed end
|
|
11
|
+
* state identical to a one-shot parse of the full source. The function is also
|
|
12
|
+
* idempotent — `repairMarkdownTail(repairMarkdownTail(x)) === repairMarkdownTail(x)`.
|
|
13
|
+
*
|
|
14
|
+
* Scope: this operates on the CommonMark/GFM subset the engine renders. Raw HTML
|
|
15
|
+
* is plain text here (no tag balancing), and `$$` math is ordinary text — neither
|
|
16
|
+
* is repaired. Open fenced code blocks are left entirely untouched so the block
|
|
17
|
+
* parser can keep rendering them as an open code block.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Repair the unsettled tail of a streamed markdown source: close unpaired inline
|
|
21
|
+
* markers and drop half-arrived links/images. A no-op on complete syntax.
|
|
22
|
+
*/
|
|
23
|
+
export declare function repairMarkdownTail(text: string): string;
|