@farming-labs/svelte-theme 0.2.35 → 0.2.37
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/package.json +3 -3
- package/src/lib/renderMarkdown.js +400 -69
- package/styles/docs.css +192 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/svelte-theme",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.37",
|
|
4
4
|
"description": "Svelte UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -122,8 +122,8 @@
|
|
|
122
122
|
"dependencies": {
|
|
123
123
|
"gray-matter": "^4.0.3",
|
|
124
124
|
"sugar-high": "^0.9.5",
|
|
125
|
-
"@farming-labs/
|
|
126
|
-
"@farming-labs/
|
|
125
|
+
"@farming-labs/svelte": "0.2.37",
|
|
126
|
+
"@farming-labs/docs": "0.2.37"
|
|
127
127
|
},
|
|
128
128
|
"peerDependencies": {
|
|
129
129
|
"svelte": ">=5.0.0"
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Markdown renderer for AI chat
|
|
2
|
+
* Markdown renderer for AI chat.
|
|
3
3
|
*
|
|
4
4
|
* Uses sugar-high for syntax highlighting in code blocks.
|
|
5
|
-
* Supports
|
|
6
|
-
*
|
|
5
|
+
* Supports fenced and streaming code blocks, loose language labels emitted by
|
|
6
|
+
* models, tables, inline code, emphasis, links, images, headings, blockquotes,
|
|
7
|
+
* horizontal rules, and lists.
|
|
7
8
|
*/
|
|
8
9
|
import { highlight } from "sugar-high";
|
|
9
10
|
|
|
11
|
+
const codeBlockTokenBoundary = String.fromCharCode(0);
|
|
12
|
+
|
|
13
|
+
const looseCodeLanguageAliases = new Map([
|
|
14
|
+
["bash", "bash"],
|
|
15
|
+
["shell", "bash"],
|
|
16
|
+
["sh", "bash"],
|
|
17
|
+
["zsh", "bash"],
|
|
18
|
+
["curl", "bash"],
|
|
19
|
+
["javascript", "js"],
|
|
20
|
+
["js", "js"],
|
|
21
|
+
["typescript", "ts"],
|
|
22
|
+
["ts", "ts"],
|
|
23
|
+
["json", "json"],
|
|
24
|
+
["python", "python"],
|
|
25
|
+
["py", "python"],
|
|
26
|
+
["http", "http"],
|
|
27
|
+
]);
|
|
28
|
+
|
|
10
29
|
function escapeHtml(s) {
|
|
11
30
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12
31
|
}
|
|
13
32
|
|
|
33
|
+
function escapeEscapedAttribute(s) {
|
|
34
|
+
return s.replace(/"/g, """);
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
function buildCodeBlock(lang, code) {
|
|
15
38
|
const trimmed = code.replace(/\n$/, "");
|
|
16
39
|
const highlighted = highlight(trimmed).replace(/<\/span>\n<span/g, "</span><span");
|
|
@@ -24,102 +47,410 @@ function buildCodeBlock(lang, code) {
|
|
|
24
47
|
);
|
|
25
48
|
}
|
|
26
49
|
|
|
27
|
-
function
|
|
50
|
+
function getCodeFenceOpening(line) {
|
|
51
|
+
const match = /^(?: {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
|
|
52
|
+
if (!match) return null;
|
|
53
|
+
|
|
54
|
+
const marker = match[1];
|
|
55
|
+
const rawInfo = match[2] ?? "";
|
|
56
|
+
if (!marker) return null;
|
|
57
|
+
if (marker.startsWith("`") && rawInfo.includes("`")) return null;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
markerChar: marker[0],
|
|
61
|
+
markerLength: marker.length,
|
|
62
|
+
lang: extractCodeFenceLanguage(rawInfo),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isCodeFenceClosing(line, fence) {
|
|
28
67
|
const trimmed = line.trim();
|
|
29
|
-
|
|
68
|
+
if (trimmed.length < fence.markerLength) return false;
|
|
69
|
+
|
|
70
|
+
for (const char of trimmed) {
|
|
71
|
+
if (char !== fence.markerChar) return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
30
75
|
}
|
|
31
76
|
|
|
32
|
-
function
|
|
33
|
-
|
|
77
|
+
function extractCodeFenceLanguage(rawInfo) {
|
|
78
|
+
const firstToken = rawInfo.trim().split(/\s+/, 1)[0] ?? "";
|
|
79
|
+
return firstToken
|
|
80
|
+
.replace(/^\{\.?/, "")
|
|
81
|
+
.replace(/^\./, "")
|
|
82
|
+
.replace(/[},].*$/, "");
|
|
34
83
|
}
|
|
35
84
|
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.replace(/^\|/, "")
|
|
41
|
-
.replace(/\|$/, "")
|
|
42
|
-
.split("|")
|
|
43
|
-
.map((c) => c.trim());
|
|
85
|
+
function normalizeLooseCodeBlocks(text) {
|
|
86
|
+
const lines = text.split("\n");
|
|
87
|
+
const output = [];
|
|
88
|
+
let i = 0;
|
|
44
89
|
|
|
45
|
-
|
|
46
|
-
|
|
90
|
+
while (i < lines.length) {
|
|
91
|
+
const lang = getLooseCodeLanguage(lines[i]);
|
|
92
|
+
if (!lang) {
|
|
93
|
+
output.push(lines[i]);
|
|
94
|
+
i += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
47
97
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
.join("");
|
|
98
|
+
const start = nextNonEmptyLineIndex(lines, i + 1);
|
|
99
|
+
if (start < 0 || !looksLikeLooseCodeLine(lines[start], lang)) {
|
|
100
|
+
output.push(lines[i]);
|
|
101
|
+
i += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
55
104
|
|
|
56
|
-
|
|
105
|
+
output.push(`\`\`\`${lang}`);
|
|
106
|
+
let j = start;
|
|
107
|
+
while (j < lines.length) {
|
|
108
|
+
if (j > start && isLooseCodeBoundary(lines, j, lang)) break;
|
|
109
|
+
output.push(lines[j]);
|
|
110
|
+
j += 1;
|
|
111
|
+
}
|
|
112
|
+
output.push("```");
|
|
113
|
+
i = j;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return output.join("\n");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getLooseCodeLanguage(line) {
|
|
120
|
+
const raw = line.trim().toLowerCase();
|
|
121
|
+
if (!/^[a-z][\w#+.-]*$/.test(raw)) return null;
|
|
122
|
+
return looseCodeLanguageAliases.get(raw) ?? null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function nextNonEmptyLineIndex(lines, startIndex) {
|
|
126
|
+
for (let i = startIndex; i < lines.length; i += 1) {
|
|
127
|
+
if (lines[i].trim()) return i;
|
|
128
|
+
}
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isLooseCodeBoundary(lines, index, lang) {
|
|
133
|
+
const line = lines[index];
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (getHeading(trimmed) || isHorizontalRule(trimmed)) return true;
|
|
136
|
+
|
|
137
|
+
if (!trimmed) {
|
|
138
|
+
const next = nextNonEmptyLineIndex(lines, index + 1);
|
|
139
|
+
return next < 0 || !looksLikeLooseCodeLine(lines[next], lang);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function looksLikeLooseCodeLine(line, lang) {
|
|
146
|
+
const trimmed = line.trim();
|
|
147
|
+
if (!trimmed) return false;
|
|
148
|
+
|
|
149
|
+
if (lang === "json") return /^[{[]/.test(trimmed);
|
|
150
|
+
if (lang === "js" || lang === "ts") {
|
|
151
|
+
return /^(?:import|export|const|let|var|function|async|await|class|type|interface|return|console\.|fetch\(|npm|pnpm|yarn|bun)\b/.test(
|
|
152
|
+
trimmed,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (lang === "python") {
|
|
156
|
+
return /^(?:from|import|def|class|if|for|while|print\(|python|pip)\b/.test(trimmed);
|
|
157
|
+
}
|
|
158
|
+
if (lang === "http") return /^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+\S+/.test(trimmed);
|
|
159
|
+
|
|
160
|
+
return /^(?:[$>#]\s*)?(?:claude|opencode|curl|npx|pnpm|npm|yarn|bun|git|node|deno|python|pip|export|cd|cat|echo|mkdir|touch|rm|cp|mv|vercel|docs)\b|^(?:--|\||&&|\\)|^[A-Z_][A-Z0-9_]*=/.test(
|
|
161
|
+
trimmed,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function replaceFencedCodeBlocks(text, codeBlocks) {
|
|
166
|
+
const lines = text.split("\n");
|
|
167
|
+
const output = [];
|
|
168
|
+
let i = 0;
|
|
169
|
+
|
|
170
|
+
while (i < lines.length) {
|
|
171
|
+
const fence = getCodeFenceOpening(lines[i]);
|
|
172
|
+
if (!fence) {
|
|
173
|
+
output.push(lines[i]);
|
|
174
|
+
i += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const codeLines = [];
|
|
179
|
+
i += 1;
|
|
180
|
+
|
|
181
|
+
while (i < lines.length && !isCodeFenceClosing(lines[i], fence)) {
|
|
182
|
+
codeLines.push(lines[i]);
|
|
183
|
+
i += 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (i < lines.length) i += 1;
|
|
187
|
+
|
|
188
|
+
codeBlocks.push(buildCodeBlock(fence.lang, codeLines.join("\n")));
|
|
189
|
+
output.push(`${codeBlockTokenBoundary}CB${codeBlocks.length - 1}${codeBlockTokenBoundary}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return output.join("\n");
|
|
57
193
|
}
|
|
58
194
|
|
|
59
195
|
export function renderMarkdown(text) {
|
|
60
196
|
if (!text) return "";
|
|
61
197
|
|
|
62
|
-
const codeBlockTokenBoundary = String.fromCharCode(0);
|
|
63
|
-
const codeBlockTokenPattern = new RegExp(
|
|
64
|
-
`${codeBlockTokenBoundary}CB(\\d+)${codeBlockTokenBoundary}`,
|
|
65
|
-
"g",
|
|
66
|
-
);
|
|
67
198
|
const codeBlocks = [];
|
|
199
|
+
const processed = replaceFencedCodeBlocks(normalizeLooseCodeBlocks(text), codeBlocks);
|
|
68
200
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
codeBlocks.push(buildCodeBlock(lang, code));
|
|
72
|
-
return `\x00CB${codeBlocks.length - 1}\x00`;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Incomplete fence (streaming): opening ``` with no closing one
|
|
76
|
-
processed = processed.replace(/```(\w*)\n([\s\S]*)$/, (_match, lang, code) => {
|
|
77
|
-
codeBlocks.push(buildCodeBlock(lang, code));
|
|
78
|
-
return `\x00CB${codeBlocks.length - 1}\x00`;
|
|
79
|
-
});
|
|
201
|
+
return renderMarkdownBlocks(processed, codeBlocks);
|
|
202
|
+
}
|
|
80
203
|
|
|
81
|
-
|
|
204
|
+
function renderMarkdownBlocks(text, codeBlocks) {
|
|
205
|
+
const lines = text.split("\n");
|
|
82
206
|
const output = [];
|
|
83
207
|
let i = 0;
|
|
84
208
|
|
|
85
209
|
while (i < lines.length) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
210
|
+
const line = lines[i];
|
|
211
|
+
const trimmed = line.trim();
|
|
212
|
+
|
|
213
|
+
if (!trimmed) {
|
|
214
|
+
i += 1;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const codeBlockIndex = getCodeBlockTokenIndex(trimmed);
|
|
219
|
+
if (codeBlockIndex !== null) {
|
|
220
|
+
output.push(codeBlocks[codeBlockIndex] ?? "");
|
|
221
|
+
i += 1;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (isHorizontalRule(trimmed)) {
|
|
226
|
+
output.push('<hr class="fd-ai-hr" />');
|
|
227
|
+
i += 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const setextHeading = getSetextHeading(lines, i);
|
|
232
|
+
if (setextHeading) {
|
|
233
|
+
output.push(
|
|
234
|
+
`<h${setextHeading.level}>${renderInlineMarkdown(setextHeading.text)}</h${setextHeading.level}>`,
|
|
235
|
+
);
|
|
236
|
+
i += 2;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const heading = getHeading(trimmed);
|
|
241
|
+
if (heading) {
|
|
242
|
+
output.push(`<h${heading.level}>${renderInlineMarkdown(heading.text)}</h${heading.level}>`);
|
|
243
|
+
i += 1;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const blockquote = collectBlockquote(lines, i);
|
|
248
|
+
if (blockquote) {
|
|
249
|
+
output.push(
|
|
250
|
+
`<blockquote>${renderMarkdownBlocks(blockquote.lines.join("\n"), codeBlocks)}</blockquote>`,
|
|
251
|
+
);
|
|
252
|
+
i = blockquote.nextIndex;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (isTableRow(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
|
|
257
|
+
const tableLines = [line];
|
|
258
|
+
i += 2;
|
|
90
259
|
while (i < lines.length && isTableRow(lines[i])) {
|
|
91
260
|
tableLines.push(lines[i]);
|
|
92
|
-
i
|
|
261
|
+
i += 1;
|
|
93
262
|
}
|
|
94
263
|
output.push(renderTable(tableLines));
|
|
95
264
|
continue;
|
|
96
265
|
}
|
|
97
|
-
|
|
98
|
-
i
|
|
266
|
+
|
|
267
|
+
const unorderedItems = collectListItems(lines, i, "unordered");
|
|
268
|
+
if (unorderedItems) {
|
|
269
|
+
output.push(`<ul>${unorderedItems.items.map((item) => renderListItem(item)).join("")}</ul>`);
|
|
270
|
+
i = unorderedItems.nextIndex;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const orderedItems = collectListItems(lines, i, "ordered");
|
|
275
|
+
if (orderedItems) {
|
|
276
|
+
output.push(`<ol>${orderedItems.items.map((item) => renderListItem(item)).join("")}</ol>`);
|
|
277
|
+
i = orderedItems.nextIndex;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const paragraphLines = [];
|
|
282
|
+
while (i < lines.length && lines[i].trim() && !isMarkdownBlockStart(lines, i)) {
|
|
283
|
+
paragraphLines.push(lines[i].trim());
|
|
284
|
+
i += 1;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
output.push(
|
|
288
|
+
`<p>${paragraphLines.map((paragraphLine) => renderInlineMarkdown(paragraphLine)).join("<br>")}</p>`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return output.join("");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getCodeBlockTokenIndex(line) {
|
|
296
|
+
const prefix = `${codeBlockTokenBoundary}CB`;
|
|
297
|
+
if (!line.startsWith(prefix) || !line.endsWith(codeBlockTokenBoundary)) return null;
|
|
298
|
+
|
|
299
|
+
const rawIndex = line.slice(prefix.length, -codeBlockTokenBoundary.length);
|
|
300
|
+
if (!/^\d+$/.test(rawIndex)) return null;
|
|
301
|
+
|
|
302
|
+
return Number(rawIndex);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getHeading(line) {
|
|
306
|
+
const match = /^(#{1,6})[ \t]+(.+?)(?:[ \t]+#+[ \t]*)?$/.exec(line);
|
|
307
|
+
if (!match) return null;
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
level: match[1].length,
|
|
311
|
+
text: match[2],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function getSetextHeading(lines, index) {
|
|
316
|
+
if (index + 1 >= lines.length) return null;
|
|
317
|
+
|
|
318
|
+
const text = lines[index].trim();
|
|
319
|
+
const underline = lines[index + 1].trim();
|
|
320
|
+
if (!text || !/^(?:=+|-+)$/.test(underline)) return null;
|
|
321
|
+
if (getHeading(text) || isHorizontalRule(text) || isBlockquoteStart(text)) return null;
|
|
322
|
+
if (isTableRow(lines[index])) return null;
|
|
323
|
+
if (collectListItems(lines, index, "unordered") || collectListItems(lines, index, "ordered"))
|
|
324
|
+
return null;
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
level: underline.startsWith("=") ? 1 : 2,
|
|
328
|
+
text,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function collectBlockquote(lines, startIndex) {
|
|
333
|
+
if (!isBlockquoteStart(lines[startIndex])) return null;
|
|
334
|
+
|
|
335
|
+
const quoteLines = [];
|
|
336
|
+
let i = startIndex;
|
|
337
|
+
|
|
338
|
+
while (i < lines.length) {
|
|
339
|
+
const match = /^(?: {0,3})>\s?(.*)$/.exec(lines[i]);
|
|
340
|
+
if (!match) break;
|
|
341
|
+
|
|
342
|
+
quoteLines.push(match[1]);
|
|
343
|
+
i += 1;
|
|
99
344
|
}
|
|
100
345
|
|
|
101
|
-
|
|
346
|
+
return quoteLines.length > 0 ? { lines: quoteLines, nextIndex: i } : null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function isBlockquoteStart(line) {
|
|
350
|
+
return /^(?: {0,3})>/.test(line);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function collectListItems(lines, startIndex, type) {
|
|
354
|
+
const pattern = type === "ordered" ? /^(?: {0,3})\d+\.\s+(.+)$/ : /^(?: {0,3})[-*+]\s+(.+)$/;
|
|
355
|
+
const items = [];
|
|
356
|
+
let i = startIndex;
|
|
357
|
+
|
|
358
|
+
while (i < lines.length) {
|
|
359
|
+
const match = pattern.exec(lines[i]);
|
|
360
|
+
if (!match) break;
|
|
361
|
+
|
|
362
|
+
items.push(match[1]);
|
|
363
|
+
i += 1;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return items.length > 0 ? { items, nextIndex: i } : null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function renderListItem(item) {
|
|
370
|
+
const task = /^\[([ xX])\]\s+(.+)$/.exec(item);
|
|
371
|
+
if (!task) return `<li>${renderInlineMarkdown(item)}</li>`;
|
|
372
|
+
|
|
373
|
+
const checked = task[1].toLowerCase() === "x" ? " checked" : "";
|
|
374
|
+
return `<li class="fd-ai-task-list-item"><input class="fd-ai-task-checkbox" type="checkbox" disabled${checked}>${renderInlineMarkdown(task[2])}</li>`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isMarkdownBlockStart(lines, index) {
|
|
378
|
+
const line = lines[index];
|
|
379
|
+
const trimmed = line.trim();
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
getCodeBlockTokenIndex(trimmed) !== null ||
|
|
383
|
+
isHorizontalRule(trimmed) ||
|
|
384
|
+
Boolean(getSetextHeading(lines, index)) ||
|
|
385
|
+
Boolean(getHeading(trimmed)) ||
|
|
386
|
+
isBlockquoteStart(line) ||
|
|
387
|
+
(isTableRow(line) && index + 1 < lines.length && isTableSeparator(lines[index + 1])) ||
|
|
388
|
+
Boolean(collectListItems(lines, index, "unordered")) ||
|
|
389
|
+
Boolean(collectListItems(lines, index, "ordered"))
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function renderInlineMarkdown(text) {
|
|
394
|
+
const inlineCodeTokens = [];
|
|
395
|
+
let result = escapeHtml(text).replace(/`([^`]+)`/g, (_match, code) => {
|
|
396
|
+
inlineCodeTokens.push(`<code>${code}</code>`);
|
|
397
|
+
return `${codeBlockTokenBoundary}IC${inlineCodeTokens.length - 1}${codeBlockTokenBoundary}`;
|
|
398
|
+
});
|
|
102
399
|
|
|
103
400
|
result = result
|
|
104
|
-
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
105
401
|
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
402
|
+
.replace(/__(.*?)__/g, "<strong>$1</strong>")
|
|
403
|
+
.replace(/~~(.*?)~~/g, "<del>$1</del>")
|
|
106
404
|
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>")
|
|
107
|
-
.replace(
|
|
108
|
-
.replace(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.replace(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
405
|
+
.replace(/(?<!_)_([^_]+)_(?!_)/g, "<em>$1</em>")
|
|
406
|
+
.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^)]*")?\)/g, (_match, alt, src) => {
|
|
407
|
+
return `<img src="${escapeEscapedAttribute(src)}" alt="${escapeEscapedAttribute(alt)}" loading="lazy">`;
|
|
408
|
+
})
|
|
409
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
|
|
410
|
+
return `<a href="${escapeEscapedAttribute(href)}">${label}</a>`;
|
|
411
|
+
})
|
|
412
|
+
.replace(/<(https?:\/\/[^<>\s]+)>/g, (_match, href) => {
|
|
413
|
+
return `<a href="${escapeEscapedAttribute(href)}">${href}</a>`;
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return result.replace(
|
|
417
|
+
new RegExp(`${codeBlockTokenBoundary}IC(\\d+)${codeBlockTokenBoundary}`, "g"),
|
|
418
|
+
(_match, idx) => inlineCodeTokens[Number(idx)] ?? "",
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function isHorizontalRule(line) {
|
|
423
|
+
return /^(?:-{3,}|\*{3,}|_{3,})$/.test(line);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function isTableRow(line) {
|
|
427
|
+
const trimmed = line.trim();
|
|
428
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|") && trimmed.includes("|");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function isTableSeparator(line) {
|
|
432
|
+
return /^\|[\s:]*-+[\s:]*(\|[\s:]*-+[\s:]*)*\|$/.test(line.trim());
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function renderTable(rows) {
|
|
436
|
+
const parseRow = (row) =>
|
|
437
|
+
row
|
|
438
|
+
.trim()
|
|
439
|
+
.replace(/^\|/, "")
|
|
440
|
+
.replace(/\|$/, "")
|
|
441
|
+
.split("|")
|
|
442
|
+
.map((c) => c.trim());
|
|
443
|
+
|
|
444
|
+
const headerCells = parseRow(rows[0]);
|
|
445
|
+
const thead = `<thead><tr>${headerCells.map((c) => `<th>${renderInlineMarkdown(c)}</th>`).join("")}</tr></thead>`;
|
|
446
|
+
|
|
447
|
+
const bodyRows = rows
|
|
448
|
+
.slice(1)
|
|
449
|
+
.map((row) => {
|
|
450
|
+
const cells = parseRow(row);
|
|
451
|
+
return `<tr>${cells.map((c) => `<td>${renderInlineMarkdown(c)}</td>`).join("")}</tr>`;
|
|
452
|
+
})
|
|
453
|
+
.join("");
|
|
454
|
+
|
|
455
|
+
return `<div class="fd-table-wrapper relative overflow-auto prose-no-margin my-6"><table>${thead}<tbody>${bodyRows}</tbody></table></div>`;
|
|
125
456
|
}
|
package/styles/docs.css
CHANGED
|
@@ -2634,20 +2634,38 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
2634
2634
|
font-size: inherit;
|
|
2635
2635
|
}
|
|
2636
2636
|
|
|
2637
|
+
#nd-docs-layout .fd-ai-bubble-ai .fd-table-wrapper {
|
|
2638
|
+
margin: 8px 0;
|
|
2639
|
+
overflow: auto;
|
|
2640
|
+
border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
|
|
2641
|
+
border-radius: var(--radius, 6px);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2637
2644
|
#nd-docs-layout .fd-ai-bubble-ai table {
|
|
2638
2645
|
width: 100%;
|
|
2639
|
-
border-collapse:
|
|
2640
|
-
|
|
2646
|
+
border-collapse: separate;
|
|
2647
|
+
border-spacing: 0;
|
|
2648
|
+
margin: 0;
|
|
2641
2649
|
font-size: 13px;
|
|
2642
2650
|
}
|
|
2643
2651
|
|
|
2644
2652
|
#nd-docs-layout .fd-ai-bubble-ai th,
|
|
2645
2653
|
#nd-docs-layout .fd-ai-bubble-ai td {
|
|
2646
|
-
border:
|
|
2654
|
+
border: 0;
|
|
2655
|
+
border-right: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
|
|
2656
|
+
border-bottom: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
|
|
2647
2657
|
padding: 6px 12px;
|
|
2648
2658
|
text-align: left;
|
|
2649
2659
|
}
|
|
2650
2660
|
|
|
2661
|
+
#nd-docs-layout .fd-ai-bubble-ai tr > :last-child {
|
|
2662
|
+
border-right: 0;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
#nd-docs-layout .fd-ai-bubble-ai tbody tr:last-child td {
|
|
2666
|
+
border-bottom: 0;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2651
2669
|
#nd-docs-layout .fd-ai-bubble-ai th {
|
|
2652
2670
|
background: var(--color-fd-muted, #1a1a2e);
|
|
2653
2671
|
font-weight: 600;
|
|
@@ -2656,9 +2674,24 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
2656
2674
|
|
|
2657
2675
|
/* ─── Markdown prose inside AI bubbles (panel/modal/popover) ─────── */
|
|
2658
2676
|
|
|
2677
|
+
#nd-docs-layout .fd-ai-bubble-ai h1,
|
|
2678
|
+
#nd-docs-layout .fd-ai-bubble-ai h2,
|
|
2679
|
+
#nd-docs-layout .fd-ai-bubble-ai h3,
|
|
2680
|
+
#nd-docs-layout .fd-ai-bubble-ai h4,
|
|
2681
|
+
#nd-docs-layout .fd-ai-bubble-ai h5,
|
|
2682
|
+
#nd-docs-layout .fd-ai-bubble-ai h6 {
|
|
2683
|
+
font-weight: 600;
|
|
2684
|
+
color: var(--color-fd-foreground, #e4e4e7);
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
#nd-docs-layout .fd-ai-bubble-ai h1 {
|
|
2688
|
+
font-size: 1.25rem;
|
|
2689
|
+
margin: 14px 0 7px;
|
|
2690
|
+
line-height: 1.25;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2659
2693
|
#nd-docs-layout .fd-ai-bubble-ai h2 {
|
|
2660
2694
|
font-size: 1.125rem;
|
|
2661
|
-
font-weight: 600;
|
|
2662
2695
|
margin: 12px 0 6px;
|
|
2663
2696
|
line-height: 1.3;
|
|
2664
2697
|
}
|
|
@@ -2672,11 +2705,17 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
2672
2705
|
|
|
2673
2706
|
#nd-docs-layout .fd-ai-bubble-ai h4 {
|
|
2674
2707
|
font-size: 0.9375rem;
|
|
2675
|
-
font-weight: 600;
|
|
2676
2708
|
margin: 8px 0 4px;
|
|
2677
2709
|
line-height: 1.4;
|
|
2678
2710
|
}
|
|
2679
2711
|
|
|
2712
|
+
#nd-docs-layout .fd-ai-bubble-ai h5,
|
|
2713
|
+
#nd-docs-layout .fd-ai-bubble-ai h6 {
|
|
2714
|
+
font-size: 0.8125rem;
|
|
2715
|
+
margin: 8px 0 4px;
|
|
2716
|
+
line-height: 1.45;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2680
2719
|
#nd-docs-layout .fd-ai-bubble-ai strong {
|
|
2681
2720
|
font-weight: 600;
|
|
2682
2721
|
color: var(--color-fd-foreground, #e4e4e7);
|
|
@@ -2696,6 +2735,58 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
2696
2735
|
margin-top: 2px;
|
|
2697
2736
|
}
|
|
2698
2737
|
|
|
2738
|
+
#nd-docs-layout .fd-ai-bubble-ai ul,
|
|
2739
|
+
#nd-docs-layout .fd-ai-bubble-ai ol {
|
|
2740
|
+
margin: 6px 0 8px;
|
|
2741
|
+
padding-left: 1.25rem;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
#nd-docs-layout .fd-ai-bubble-ai li {
|
|
2745
|
+
margin: 3px 0;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
#nd-docs-layout .fd-ai-bubble-ai blockquote {
|
|
2749
|
+
margin: 8px 0;
|
|
2750
|
+
padding: 6px 0 6px 12px;
|
|
2751
|
+
border-left: 2px solid var(--color-fd-border, rgba(255, 255, 255, 0.16));
|
|
2752
|
+
color: var(--color-fd-muted-foreground, #a1a1aa);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
#nd-docs-layout .fd-ai-bubble-ai blockquote p:last-child {
|
|
2756
|
+
margin-bottom: 0;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
#nd-docs-layout .fd-ai-bubble-ai .fd-ai-hr {
|
|
2760
|
+
margin: 12px 0;
|
|
2761
|
+
border: none;
|
|
2762
|
+
border-top: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.12));
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
#nd-docs-layout .fd-ai-bubble-ai del {
|
|
2766
|
+
color: var(--color-fd-muted-foreground, #a1a1aa);
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
#nd-docs-layout .fd-ai-bubble-ai .fd-ai-task-list-item {
|
|
2770
|
+
list-style: none;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
#nd-docs-layout .fd-ai-bubble-ai .fd-ai-task-checkbox {
|
|
2774
|
+
width: 0.875rem;
|
|
2775
|
+
height: 0.875rem;
|
|
2776
|
+
margin: 0 0.45rem 0 -1.25rem;
|
|
2777
|
+
vertical-align: -0.12em;
|
|
2778
|
+
accent-color: var(--color-fd-primary, #6366f1);
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
#nd-docs-layout .fd-ai-bubble-ai img {
|
|
2782
|
+
display: block;
|
|
2783
|
+
max-width: 100%;
|
|
2784
|
+
height: auto;
|
|
2785
|
+
margin: 8px 0;
|
|
2786
|
+
border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.12));
|
|
2787
|
+
border-radius: var(--radius, 4px);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2699
2790
|
#nd-docs-layout .fd-ai-bubble-ai pre {
|
|
2700
2791
|
margin: 8px 0;
|
|
2701
2792
|
border-radius: 0;
|
|
@@ -3100,20 +3191,38 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
3100
3191
|
text-decoration: underline;
|
|
3101
3192
|
}
|
|
3102
3193
|
|
|
3194
|
+
#nd-docs-layout .fd-ai-fm-msg-content .fd-table-wrapper {
|
|
3195
|
+
margin: 8px 0;
|
|
3196
|
+
overflow: auto;
|
|
3197
|
+
border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
|
|
3198
|
+
border-radius: var(--radius, 6px);
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3103
3201
|
#nd-docs-layout .fd-ai-fm-msg-content table {
|
|
3104
3202
|
width: 100%;
|
|
3105
|
-
border-collapse:
|
|
3106
|
-
|
|
3203
|
+
border-collapse: separate;
|
|
3204
|
+
border-spacing: 0;
|
|
3205
|
+
margin: 0;
|
|
3107
3206
|
font-size: 13px;
|
|
3108
3207
|
}
|
|
3109
3208
|
|
|
3110
3209
|
#nd-docs-layout .fd-ai-fm-msg-content th,
|
|
3111
3210
|
#nd-docs-layout .fd-ai-fm-msg-content td {
|
|
3112
|
-
border:
|
|
3211
|
+
border: 0;
|
|
3212
|
+
border-right: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
|
|
3213
|
+
border-bottom: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
|
|
3113
3214
|
padding: 6px 12px;
|
|
3114
3215
|
text-align: left;
|
|
3115
3216
|
}
|
|
3116
3217
|
|
|
3218
|
+
#nd-docs-layout .fd-ai-fm-msg-content tr > :last-child {
|
|
3219
|
+
border-right: 0;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
#nd-docs-layout .fd-ai-fm-msg-content tbody tr:last-child td {
|
|
3223
|
+
border-bottom: 0;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3117
3226
|
#nd-docs-layout .fd-ai-fm-msg-content th {
|
|
3118
3227
|
background: var(--color-fd-muted, #1a1a2e);
|
|
3119
3228
|
font-weight: 600;
|
|
@@ -3122,9 +3231,24 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
3122
3231
|
|
|
3123
3232
|
/* ─── Markdown prose inside full-modal messages ──────────────────── */
|
|
3124
3233
|
|
|
3234
|
+
#nd-docs-layout .fd-ai-fm-msg-content h1,
|
|
3235
|
+
#nd-docs-layout .fd-ai-fm-msg-content h2,
|
|
3236
|
+
#nd-docs-layout .fd-ai-fm-msg-content h3,
|
|
3237
|
+
#nd-docs-layout .fd-ai-fm-msg-content h4,
|
|
3238
|
+
#nd-docs-layout .fd-ai-fm-msg-content h5,
|
|
3239
|
+
#nd-docs-layout .fd-ai-fm-msg-content h6 {
|
|
3240
|
+
font-weight: 600;
|
|
3241
|
+
color: var(--color-fd-foreground, #e4e4e7);
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
#nd-docs-layout .fd-ai-fm-msg-content h1 {
|
|
3245
|
+
font-size: 1.375rem;
|
|
3246
|
+
margin: 18px 0 9px;
|
|
3247
|
+
line-height: 1.25;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3125
3250
|
#nd-docs-layout .fd-ai-fm-msg-content h2 {
|
|
3126
3251
|
font-size: 1.25rem;
|
|
3127
|
-
font-weight: 600;
|
|
3128
3252
|
margin: 16px 0 8px;
|
|
3129
3253
|
line-height: 1.3;
|
|
3130
3254
|
}
|
|
@@ -3138,11 +3262,17 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
3138
3262
|
|
|
3139
3263
|
#nd-docs-layout .fd-ai-fm-msg-content h4 {
|
|
3140
3264
|
font-size: 1rem;
|
|
3141
|
-
font-weight: 600;
|
|
3142
3265
|
margin: 10px 0 4px;
|
|
3143
3266
|
line-height: 1.4;
|
|
3144
3267
|
}
|
|
3145
3268
|
|
|
3269
|
+
#nd-docs-layout .fd-ai-fm-msg-content h5,
|
|
3270
|
+
#nd-docs-layout .fd-ai-fm-msg-content h6 {
|
|
3271
|
+
font-size: 0.875rem;
|
|
3272
|
+
margin: 10px 0 4px;
|
|
3273
|
+
line-height: 1.45;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3146
3276
|
#nd-docs-layout .fd-ai-fm-msg-content strong {
|
|
3147
3277
|
font-weight: 600;
|
|
3148
3278
|
color: var(--color-fd-foreground, #e4e4e7);
|
|
@@ -3162,6 +3292,58 @@ html.dark #nd-docs-layout pre.shiki {
|
|
|
3162
3292
|
margin-top: 2px;
|
|
3163
3293
|
}
|
|
3164
3294
|
|
|
3295
|
+
#nd-docs-layout .fd-ai-fm-msg-content ul,
|
|
3296
|
+
#nd-docs-layout .fd-ai-fm-msg-content ol {
|
|
3297
|
+
margin: 8px 0 10px;
|
|
3298
|
+
padding-left: 1.35rem;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
#nd-docs-layout .fd-ai-fm-msg-content li {
|
|
3302
|
+
margin: 4px 0;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
#nd-docs-layout .fd-ai-fm-msg-content blockquote {
|
|
3306
|
+
margin: 10px 0;
|
|
3307
|
+
padding: 7px 0 7px 14px;
|
|
3308
|
+
border-left: 2px solid var(--color-fd-border, rgba(255, 255, 255, 0.16));
|
|
3309
|
+
color: var(--color-fd-muted-foreground, #a1a1aa);
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
#nd-docs-layout .fd-ai-fm-msg-content blockquote p:last-child {
|
|
3313
|
+
margin-bottom: 0;
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
#nd-docs-layout .fd-ai-fm-msg-content .fd-ai-hr {
|
|
3317
|
+
margin: 14px 0;
|
|
3318
|
+
border: none;
|
|
3319
|
+
border-top: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.12));
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
#nd-docs-layout .fd-ai-fm-msg-content del {
|
|
3323
|
+
color: var(--color-fd-muted-foreground, #a1a1aa);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
#nd-docs-layout .fd-ai-fm-msg-content .fd-ai-task-list-item {
|
|
3327
|
+
list-style: none;
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
#nd-docs-layout .fd-ai-fm-msg-content .fd-ai-task-checkbox {
|
|
3331
|
+
width: 0.9rem;
|
|
3332
|
+
height: 0.9rem;
|
|
3333
|
+
margin: 0 0.5rem 0 -1.35rem;
|
|
3334
|
+
vertical-align: -0.12em;
|
|
3335
|
+
accent-color: var(--color-fd-primary, #6366f1);
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
#nd-docs-layout .fd-ai-fm-msg-content img {
|
|
3339
|
+
display: block;
|
|
3340
|
+
max-width: 100%;
|
|
3341
|
+
height: auto;
|
|
3342
|
+
margin: 10px 0;
|
|
3343
|
+
border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.12));
|
|
3344
|
+
border-radius: var(--radius, 4px);
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3165
3347
|
#nd-docs-layout .fd-ai-fm-msg-content pre {
|
|
3166
3348
|
margin: 8px 0;
|
|
3167
3349
|
border-radius: 0;
|