@vectojs/markdown 0.18.2 → 0.19.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/Markdown.d.ts +48 -3
- package/dist/blockAffordances.d.ts +60 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +562 -61
- package/dist/index.mjs +560 -61
- package/dist/markdown-code.d.ts +136 -5
- package/dist/theme.d.ts +21 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ __export(index_exports, {
|
|
|
44
44
|
extensionForLanguage: () => extensionForLanguage,
|
|
45
45
|
footnoteMarker: () => footnoteMarker,
|
|
46
46
|
hasFencedBlockRenderer: () => hasFencedBlockRenderer,
|
|
47
|
+
highlightedLanguages: () => highlightedLanguages,
|
|
47
48
|
isFencedBlockRendererReady: () => isFencedBlockRendererReady,
|
|
48
49
|
isMathJaxReady: () => isMathJaxReady,
|
|
49
50
|
isPresetName: () => isPresetName,
|
|
@@ -52,6 +53,7 @@ __export(index_exports, {
|
|
|
52
53
|
preloadMathJax: () => preloadMathJax,
|
|
53
54
|
registerFencedBlockRenderer: () => registerFencedBlockRenderer,
|
|
54
55
|
renderFencedBlock: () => renderFencedBlock,
|
|
56
|
+
resolveBlockAffordanceConfig: () => resolveBlockAffordanceConfig,
|
|
55
57
|
resolvePresetTheme: () => resolvePresetTheme,
|
|
56
58
|
scanFrontMatter: () => scanFrontMatter,
|
|
57
59
|
tableContentOf: () => tableContentOf,
|
|
@@ -606,11 +608,13 @@ var DEFAULT_THEME = {
|
|
|
606
608
|
syntaxStringColor: "#86efac",
|
|
607
609
|
syntaxCommentColor: "#64748b",
|
|
608
610
|
syntaxNumberColor: "#fbbf24",
|
|
611
|
+
codeLangColor: "#64748b",
|
|
609
612
|
bodyFont: "Inter, system-ui, sans-serif",
|
|
610
613
|
codeFont: 'ui-monospace, "JetBrains Mono", "Fira Code", monospace',
|
|
611
614
|
fontSize: 16,
|
|
612
615
|
headingSizes: [32, 28, 24, 20, 18, 16],
|
|
613
616
|
codeFontSize: 15,
|
|
617
|
+
codeLangFontSize: 12,
|
|
614
618
|
tableFontSize: 14,
|
|
615
619
|
footnoteMarkerScale: 0.75,
|
|
616
620
|
subscriptScale: 0.75,
|
|
@@ -645,6 +649,12 @@ function resolveTheme(theme) {
|
|
|
645
649
|
if (theme?.footnoteColor === void 0) {
|
|
646
650
|
merged.footnoteColor = merged.linkColor;
|
|
647
651
|
}
|
|
652
|
+
if (theme?.codeLangColor === void 0) {
|
|
653
|
+
merged.codeLangColor = merged.syntaxCommentColor;
|
|
654
|
+
}
|
|
655
|
+
if (theme?.codeLangFontSize === void 0) {
|
|
656
|
+
merged.codeLangFontSize = Math.max(1, merged.codeFontSize - 3);
|
|
657
|
+
}
|
|
648
658
|
return merged;
|
|
649
659
|
}
|
|
650
660
|
function headingSize(theme, depth) {
|
|
@@ -984,14 +994,215 @@ var KEYWORD_SETS = {
|
|
|
984
994
|
"static"
|
|
985
995
|
])
|
|
986
996
|
};
|
|
997
|
+
KEYWORD_SETS["bash"] = /* @__PURE__ */ new Set([
|
|
998
|
+
// Shell builtins and control words. Deliberately not the whole of coreutils:
|
|
999
|
+
// a keyword table that includes every command name colors an entire script
|
|
1000
|
+
// uniformly, which reads worse than coloring only the control flow.
|
|
1001
|
+
"if",
|
|
1002
|
+
"then",
|
|
1003
|
+
"else",
|
|
1004
|
+
"elif",
|
|
1005
|
+
"fi",
|
|
1006
|
+
"for",
|
|
1007
|
+
"while",
|
|
1008
|
+
"until",
|
|
1009
|
+
"do",
|
|
1010
|
+
"done",
|
|
1011
|
+
"case",
|
|
1012
|
+
"esac",
|
|
1013
|
+
"in",
|
|
1014
|
+
"function",
|
|
1015
|
+
"return",
|
|
1016
|
+
"exit",
|
|
1017
|
+
"break",
|
|
1018
|
+
"continue",
|
|
1019
|
+
"local",
|
|
1020
|
+
"export",
|
|
1021
|
+
"readonly",
|
|
1022
|
+
"declare",
|
|
1023
|
+
"unset",
|
|
1024
|
+
"shift",
|
|
1025
|
+
"source",
|
|
1026
|
+
"alias",
|
|
1027
|
+
"set",
|
|
1028
|
+
"trap",
|
|
1029
|
+
"echo",
|
|
1030
|
+
"cd",
|
|
1031
|
+
"sudo",
|
|
1032
|
+
"true",
|
|
1033
|
+
"false"
|
|
1034
|
+
]);
|
|
1035
|
+
KEYWORD_SETS["json"] = /* @__PURE__ */ new Set(["true", "false", "null"]);
|
|
1036
|
+
KEYWORD_SETS["css"] = /* @__PURE__ */ new Set([
|
|
1037
|
+
"important",
|
|
1038
|
+
"inherit",
|
|
1039
|
+
"initial",
|
|
1040
|
+
"unset",
|
|
1041
|
+
"revert",
|
|
1042
|
+
"auto",
|
|
1043
|
+
"none",
|
|
1044
|
+
"var",
|
|
1045
|
+
"calc"
|
|
1046
|
+
]);
|
|
1047
|
+
KEYWORD_SETS["html"] = /* @__PURE__ */ new Set([
|
|
1048
|
+
// Tag names are the meaningful tokens a reader scans for. The tokenizer is
|
|
1049
|
+
// word-based, so `<div>` yields the word `div`.
|
|
1050
|
+
"html",
|
|
1051
|
+
"head",
|
|
1052
|
+
"body",
|
|
1053
|
+
"title",
|
|
1054
|
+
"meta",
|
|
1055
|
+
"link",
|
|
1056
|
+
"script",
|
|
1057
|
+
"style",
|
|
1058
|
+
"div",
|
|
1059
|
+
"span",
|
|
1060
|
+
"p",
|
|
1061
|
+
"a",
|
|
1062
|
+
"img",
|
|
1063
|
+
"ul",
|
|
1064
|
+
"ol",
|
|
1065
|
+
"li",
|
|
1066
|
+
"table",
|
|
1067
|
+
"tr",
|
|
1068
|
+
"td",
|
|
1069
|
+
"th",
|
|
1070
|
+
"form",
|
|
1071
|
+
"input",
|
|
1072
|
+
"button",
|
|
1073
|
+
"label",
|
|
1074
|
+
"select",
|
|
1075
|
+
"option",
|
|
1076
|
+
"textarea",
|
|
1077
|
+
"header",
|
|
1078
|
+
"footer",
|
|
1079
|
+
"nav",
|
|
1080
|
+
"main",
|
|
1081
|
+
"section",
|
|
1082
|
+
"article",
|
|
1083
|
+
"aside",
|
|
1084
|
+
"canvas",
|
|
1085
|
+
"svg",
|
|
1086
|
+
"template",
|
|
1087
|
+
"slot"
|
|
1088
|
+
]);
|
|
987
1089
|
KEYWORD_SETS["javascript"] = KEYWORD_SETS["js"];
|
|
988
1090
|
KEYWORD_SETS["typescript"] = KEYWORD_SETS["ts"];
|
|
989
1091
|
KEYWORD_SETS["python"] = KEYWORD_SETS["py"];
|
|
990
1092
|
KEYWORD_SETS["rs"] = KEYWORD_SETS["rust"];
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1093
|
+
KEYWORD_SETS["jsx"] = KEYWORD_SETS["js"];
|
|
1094
|
+
KEYWORD_SETS["mjs"] = KEYWORD_SETS["js"];
|
|
1095
|
+
KEYWORD_SETS["cjs"] = KEYWORD_SETS["js"];
|
|
1096
|
+
KEYWORD_SETS["tsx"] = KEYWORD_SETS["ts"];
|
|
1097
|
+
KEYWORD_SETS["mts"] = KEYWORD_SETS["ts"];
|
|
1098
|
+
KEYWORD_SETS["cts"] = KEYWORD_SETS["ts"];
|
|
1099
|
+
KEYWORD_SETS["sh"] = KEYWORD_SETS["bash"];
|
|
1100
|
+
KEYWORD_SETS["zsh"] = KEYWORD_SETS["bash"];
|
|
1101
|
+
KEYWORD_SETS["shell"] = KEYWORD_SETS["bash"];
|
|
1102
|
+
KEYWORD_SETS["console"] = KEYWORD_SETS["bash"];
|
|
1103
|
+
KEYWORD_SETS["jsonc"] = KEYWORD_SETS["json"];
|
|
1104
|
+
KEYWORD_SETS["json5"] = KEYWORD_SETS["json"];
|
|
1105
|
+
KEYWORD_SETS["scss"] = KEYWORD_SETS["css"];
|
|
1106
|
+
KEYWORD_SETS["sass"] = KEYWORD_SETS["css"];
|
|
1107
|
+
KEYWORD_SETS["less"] = KEYWORD_SETS["css"];
|
|
1108
|
+
KEYWORD_SETS["vue"] = KEYWORD_SETS["html"];
|
|
1109
|
+
KEYWORD_SETS["svelte"] = KEYWORD_SETS["html"];
|
|
1110
|
+
KEYWORD_SETS["xml"] = KEYWORD_SETS["html"];
|
|
1111
|
+
KEYWORD_SETS["svg"] = KEYWORD_SETS["html"];
|
|
1112
|
+
var C_LIKE = {
|
|
1113
|
+
lineComments: ["//"],
|
|
1114
|
+
quotes: ['"', "'", "`"],
|
|
1115
|
+
numbers: true,
|
|
1116
|
+
blockComments: [["/*", "*/"]],
|
|
1117
|
+
// A JS/TS template literal spans lines. Listed here as well as in `quotes`:
|
|
1118
|
+
// `quotes` handles the common single-line case, and this carries the rest.
|
|
1119
|
+
multilineStrings: ["`"]
|
|
1120
|
+
};
|
|
1121
|
+
var HASH_COMMENT = {
|
|
1122
|
+
lineComments: ["#"],
|
|
1123
|
+
quotes: ['"', "'"],
|
|
1124
|
+
numbers: true
|
|
1125
|
+
};
|
|
1126
|
+
var LANGUAGE_SYNTAX = {
|
|
1127
|
+
js: C_LIKE,
|
|
1128
|
+
ts: C_LIKE,
|
|
1129
|
+
// A Python docstring is the language's block comment in practice, and it is
|
|
1130
|
+
// lexically a string, so it is carried as one rather than invented as a third
|
|
1131
|
+
// kind. Triple delimiters are listed before the single ones so the longest
|
|
1132
|
+
// match wins.
|
|
1133
|
+
py: { ...HASH_COMMENT, multilineStrings: ['"""', "'''"] },
|
|
1134
|
+
// Rust has `//` line comments AND `'` lifetimes. The unterminated-quote
|
|
1135
|
+
// fallback already keeps a lifetime from swallowing the line, so `'` stays
|
|
1136
|
+
// listed: `'a'` is a valid char literal and should color as a string.
|
|
1137
|
+
rust: C_LIKE,
|
|
1138
|
+
bash: HASH_COMMENT,
|
|
1139
|
+
// JSON has no comments and no single-quoted strings. JSONC does have `//`,
|
|
1140
|
+
// and is aliased separately below rather than sharing this entry.
|
|
1141
|
+
json: { lineComments: [], quotes: ['"'], numbers: true },
|
|
1142
|
+
// CSS has only block comments — which now span lines, so this entry claims
|
|
1143
|
+
// them. Numbers are everywhere in CSS and coloring them is most of the visible
|
|
1144
|
+
// benefit.
|
|
1145
|
+
css: {
|
|
1146
|
+
lineComments: [],
|
|
1147
|
+
quotes: ['"', "'"],
|
|
1148
|
+
numbers: true,
|
|
1149
|
+
blockComments: [["/*", "*/"]]
|
|
1150
|
+
},
|
|
1151
|
+
// Markup: no line comments, and numbers inside attribute values are noise
|
|
1152
|
+
// rather than signal. An SGML comment spans lines like any other block form.
|
|
1153
|
+
html: {
|
|
1154
|
+
lineComments: [],
|
|
1155
|
+
quotes: ['"', "'"],
|
|
1156
|
+
numbers: false,
|
|
1157
|
+
blockComments: [["<!--", "-->"]]
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
LANGUAGE_SYNTAX["javascript"] = LANGUAGE_SYNTAX["js"];
|
|
1161
|
+
LANGUAGE_SYNTAX["typescript"] = LANGUAGE_SYNTAX["ts"];
|
|
1162
|
+
LANGUAGE_SYNTAX["python"] = LANGUAGE_SYNTAX["py"];
|
|
1163
|
+
LANGUAGE_SYNTAX["rs"] = LANGUAGE_SYNTAX["rust"];
|
|
1164
|
+
LANGUAGE_SYNTAX["jsx"] = LANGUAGE_SYNTAX["js"];
|
|
1165
|
+
LANGUAGE_SYNTAX["mjs"] = LANGUAGE_SYNTAX["js"];
|
|
1166
|
+
LANGUAGE_SYNTAX["cjs"] = LANGUAGE_SYNTAX["js"];
|
|
1167
|
+
LANGUAGE_SYNTAX["tsx"] = LANGUAGE_SYNTAX["ts"];
|
|
1168
|
+
LANGUAGE_SYNTAX["mts"] = LANGUAGE_SYNTAX["ts"];
|
|
1169
|
+
LANGUAGE_SYNTAX["cts"] = LANGUAGE_SYNTAX["ts"];
|
|
1170
|
+
LANGUAGE_SYNTAX["sh"] = LANGUAGE_SYNTAX["bash"];
|
|
1171
|
+
LANGUAGE_SYNTAX["zsh"] = LANGUAGE_SYNTAX["bash"];
|
|
1172
|
+
LANGUAGE_SYNTAX["shell"] = LANGUAGE_SYNTAX["bash"];
|
|
1173
|
+
LANGUAGE_SYNTAX["console"] = LANGUAGE_SYNTAX["bash"];
|
|
1174
|
+
LANGUAGE_SYNTAX["yaml"] = HASH_COMMENT;
|
|
1175
|
+
LANGUAGE_SYNTAX["yml"] = HASH_COMMENT;
|
|
1176
|
+
LANGUAGE_SYNTAX["toml"] = HASH_COMMENT;
|
|
1177
|
+
LANGUAGE_SYNTAX["ini"] = HASH_COMMENT;
|
|
1178
|
+
LANGUAGE_SYNTAX["dockerfile"] = HASH_COMMENT;
|
|
1179
|
+
LANGUAGE_SYNTAX["makefile"] = HASH_COMMENT;
|
|
1180
|
+
LANGUAGE_SYNTAX["make"] = HASH_COMMENT;
|
|
1181
|
+
LANGUAGE_SYNTAX["jsonc"] = { lineComments: ["//"], quotes: ['"'], numbers: true };
|
|
1182
|
+
LANGUAGE_SYNTAX["json5"] = { lineComments: ["//"], quotes: ['"', "'"], numbers: true };
|
|
1183
|
+
LANGUAGE_SYNTAX["scss"] = C_LIKE;
|
|
1184
|
+
LANGUAGE_SYNTAX["sass"] = C_LIKE;
|
|
1185
|
+
LANGUAGE_SYNTAX["less"] = C_LIKE;
|
|
1186
|
+
LANGUAGE_SYNTAX["glsl"] = C_LIKE;
|
|
1187
|
+
LANGUAGE_SYNTAX["c"] = C_LIKE;
|
|
1188
|
+
LANGUAGE_SYNTAX["cpp"] = C_LIKE;
|
|
1189
|
+
LANGUAGE_SYNTAX["go"] = C_LIKE;
|
|
1190
|
+
LANGUAGE_SYNTAX["java"] = C_LIKE;
|
|
1191
|
+
LANGUAGE_SYNTAX["kotlin"] = C_LIKE;
|
|
1192
|
+
LANGUAGE_SYNTAX["swift"] = C_LIKE;
|
|
1193
|
+
LANGUAGE_SYNTAX["vue"] = LANGUAGE_SYNTAX["html"];
|
|
1194
|
+
LANGUAGE_SYNTAX["svelte"] = LANGUAGE_SYNTAX["html"];
|
|
1195
|
+
LANGUAGE_SYNTAX["xml"] = LANGUAGE_SYNTAX["html"];
|
|
1196
|
+
LANGUAGE_SYNTAX["svg"] = LANGUAGE_SYNTAX["html"];
|
|
1197
|
+
function highlightedLanguages() {
|
|
1198
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_SYNTAX), ...Object.keys(KEYWORD_SETS)])].sort();
|
|
1199
|
+
}
|
|
1200
|
+
function highlightLine(line, lang, theme, carry = null) {
|
|
1201
|
+
const key = lang.trim().toLowerCase().split(/[\s:,{]/)[0] ?? "";
|
|
1202
|
+
const keywords = KEYWORD_SETS[key];
|
|
1203
|
+
const syntax = LANGUAGE_SYNTAX[key];
|
|
1204
|
+
if (!keywords && !syntax) {
|
|
1205
|
+
return { segments: [{ text: line, color: theme.codeColor }], carry: null };
|
|
995
1206
|
}
|
|
996
1207
|
const segments = [];
|
|
997
1208
|
const KEYWORD_COLOR = theme.syntaxKeywordColor;
|
|
@@ -1006,19 +1217,63 @@ function highlightLine(line, lang, theme) {
|
|
|
1006
1217
|
buf = "";
|
|
1007
1218
|
}
|
|
1008
1219
|
};
|
|
1220
|
+
const lexical = syntax ?? C_LIKE;
|
|
1221
|
+
const findClose = (from, close, isString) => {
|
|
1222
|
+
let j = from;
|
|
1223
|
+
while (j < line.length) {
|
|
1224
|
+
if (isString && line[j] === "\\") {
|
|
1225
|
+
j += 2;
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (line.startsWith(close, j)) return j + close.length;
|
|
1229
|
+
j++;
|
|
1230
|
+
}
|
|
1231
|
+
return -1;
|
|
1232
|
+
};
|
|
1233
|
+
if (carry) {
|
|
1234
|
+
const color = carry.kind === "comment" ? COMMENT_COLOR : STRING_COLOR;
|
|
1235
|
+
const end = findClose(0, carry.close, carry.kind === "string");
|
|
1236
|
+
if (end === -1) {
|
|
1237
|
+
if (line.length > 0) segments.push({ text: line, color });
|
|
1238
|
+
return { segments, carry };
|
|
1239
|
+
}
|
|
1240
|
+
segments.push({ text: line.slice(0, end), color });
|
|
1241
|
+
i = end;
|
|
1242
|
+
}
|
|
1009
1243
|
while (i < line.length) {
|
|
1010
1244
|
const ch = line[i];
|
|
1011
|
-
|
|
1245
|
+
const block = lexical.blockComments?.find(([open]) => line.startsWith(open, i));
|
|
1246
|
+
if (block) {
|
|
1247
|
+
const [open, close] = block;
|
|
1012
1248
|
flush(theme.codeColor);
|
|
1013
|
-
|
|
1014
|
-
|
|
1249
|
+
const end = findClose(i + open.length, close, false);
|
|
1250
|
+
if (end === -1) {
|
|
1251
|
+
segments.push({ text: line.slice(i), color: COMMENT_COLOR });
|
|
1252
|
+
return { segments, carry: { kind: "comment", close } };
|
|
1253
|
+
}
|
|
1254
|
+
segments.push({ text: line.slice(i, end), color: COMMENT_COLOR });
|
|
1255
|
+
i = end;
|
|
1256
|
+
continue;
|
|
1015
1257
|
}
|
|
1016
|
-
|
|
1258
|
+
const comment = lexical.lineComments.find((prefix) => line.startsWith(prefix, i));
|
|
1259
|
+
if (comment !== void 0) {
|
|
1017
1260
|
flush(theme.codeColor);
|
|
1018
1261
|
segments.push({ text: line.slice(i), color: COMMENT_COLOR });
|
|
1019
|
-
return segments;
|
|
1262
|
+
return { segments, carry: null };
|
|
1020
1263
|
}
|
|
1021
|
-
|
|
1264
|
+
const multi = lexical.multilineStrings?.find((delim) => line.startsWith(delim, i));
|
|
1265
|
+
if (multi !== void 0) {
|
|
1266
|
+
flush(theme.codeColor);
|
|
1267
|
+
const end = findClose(i + multi.length, multi, true);
|
|
1268
|
+
if (end === -1) {
|
|
1269
|
+
segments.push({ text: line.slice(i), color: STRING_COLOR });
|
|
1270
|
+
return { segments, carry: { kind: "string", close: multi } };
|
|
1271
|
+
}
|
|
1272
|
+
segments.push({ text: line.slice(i, end), color: STRING_COLOR });
|
|
1273
|
+
i = end;
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (lexical.quotes.includes(ch)) {
|
|
1022
1277
|
const quote = ch;
|
|
1023
1278
|
let j = i + 1;
|
|
1024
1279
|
let closed = false;
|
|
@@ -1043,7 +1298,7 @@ function highlightLine(line, lang, theme) {
|
|
|
1043
1298
|
i++;
|
|
1044
1299
|
continue;
|
|
1045
1300
|
}
|
|
1046
|
-
if (/\d/.test(ch) && (i === 0 || /[\s(,=+\-*/<>[\]{}:;]/.test(line[i - 1]))) {
|
|
1301
|
+
if (lexical.numbers && /\d/.test(ch) && (i === 0 || /[\s(,=+\-*/<>[\]{}:;]/.test(line[i - 1]))) {
|
|
1047
1302
|
flush(theme.codeColor);
|
|
1048
1303
|
let j = i;
|
|
1049
1304
|
while (j < line.length && /[\d._xXa-fA-F]/.test(line[j])) j++;
|
|
@@ -1058,7 +1313,9 @@ function highlightLine(line, lang, theme) {
|
|
|
1058
1313
|
const word = line.slice(i, j);
|
|
1059
1314
|
segments.push({
|
|
1060
1315
|
text: word,
|
|
1061
|
-
|
|
1316
|
+
// A language may have lexical syntax but no keywords (plain YAML, TOML,
|
|
1317
|
+
// a Dockerfile). Those still get comments, strings and numbers.
|
|
1318
|
+
color: keywords?.has(word) ? KEYWORD_COLOR : theme.codeColor
|
|
1062
1319
|
});
|
|
1063
1320
|
i = j;
|
|
1064
1321
|
continue;
|
|
@@ -1067,10 +1324,18 @@ function highlightLine(line, lang, theme) {
|
|
|
1067
1324
|
i++;
|
|
1068
1325
|
}
|
|
1069
1326
|
flush(theme.codeColor);
|
|
1070
|
-
return segments;
|
|
1327
|
+
return { segments, carry: null };
|
|
1071
1328
|
}
|
|
1072
1329
|
var CodeBlock = class extends import_ui.UIComponent {
|
|
1073
1330
|
lines;
|
|
1331
|
+
/**
|
|
1332
|
+
* Lexical state ENTERING each line, index-aligned with {@link lines}.
|
|
1333
|
+
*
|
|
1334
|
+
* Entering rather than leaving, so a streamed append can resume tokenizing at
|
|
1335
|
+
* the prefix-reuse boundary by reading one entry instead of re-scanning the
|
|
1336
|
+
* document for an unclosed block comment.
|
|
1337
|
+
*/
|
|
1338
|
+
lineCarry = [];
|
|
1074
1339
|
grid = null;
|
|
1075
1340
|
/** Raw (unhighlighted) lines of the last build, for prefix reuse in buildLines. */
|
|
1076
1341
|
rawLines = null;
|
|
@@ -1078,6 +1343,19 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1078
1343
|
source;
|
|
1079
1344
|
/** Bumped by {@link buildLines} and {@link setSelectable}; read by `Scene`. */
|
|
1080
1345
|
contentEpoch = 0;
|
|
1346
|
+
/**
|
|
1347
|
+
* Horizontal scroll offset in local px, always in `[0, maxScrollX]`.
|
|
1348
|
+
*
|
|
1349
|
+
* Code does not wrap, so a line wider than the box would otherwise have an
|
|
1350
|
+
* unreachable tail. This offset is subtracted from BOTH the painted cell x and
|
|
1351
|
+
* the projected line x in the same frame — never one without the other, or the
|
|
1352
|
+
* DOM selection carriers detach from the glyphs they are supposed to cover
|
|
1353
|
+
* (the defect class `5cf7119` and `ee1de6f` fixed on the vertical axis).
|
|
1354
|
+
*/
|
|
1355
|
+
scrollXValue = 0;
|
|
1356
|
+
/** Memoized widest prepared line, keyed by the grid identity it came from. */
|
|
1357
|
+
contentWidthGrid = null;
|
|
1358
|
+
contentWidthValue = 0;
|
|
1081
1359
|
lang;
|
|
1082
1360
|
theme;
|
|
1083
1361
|
/**
|
|
@@ -1089,6 +1367,10 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1089
1367
|
pad;
|
|
1090
1368
|
codeFont;
|
|
1091
1369
|
selectable;
|
|
1370
|
+
/** Whether the language header band is drawn. See {@link CodeBlockOptions.showLanguage}. */
|
|
1371
|
+
showLanguage;
|
|
1372
|
+
/** Font of the header label, resolved once from the theme. */
|
|
1373
|
+
langFont;
|
|
1092
1374
|
/**
|
|
1093
1375
|
* @param theme Any subset of {@link MarkdownTheme}, or the name of a built-in
|
|
1094
1376
|
* preset (see {@link MarkdownThemePresetName}). Accepting a partial theme
|
|
@@ -1100,7 +1382,7 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1100
1382
|
* be constructed directly with a preset name without going through
|
|
1101
1383
|
* `Markdown`.
|
|
1102
1384
|
*/
|
|
1103
|
-
constructor(code, lang, maxWidth, theme, selectable = true) {
|
|
1385
|
+
constructor(code, lang, maxWidth, theme, selectable = true, options = {}) {
|
|
1104
1386
|
super();
|
|
1105
1387
|
const resolved = resolvePresetTheme(theme);
|
|
1106
1388
|
this.source = code;
|
|
@@ -1110,15 +1392,129 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1110
1392
|
this.pad = resolved.codePadding;
|
|
1111
1393
|
this.codeFont = `${resolved.codeFontSize}px ${resolved.codeFont}`;
|
|
1112
1394
|
this.selectable = selectable;
|
|
1395
|
+
this.langFont = `${resolved.codeLangFontSize}px ${resolved.codeFont}`;
|
|
1396
|
+
this.showLanguage = options.showLanguage === true && this.languageLabel() !== "";
|
|
1113
1397
|
this.lines = [];
|
|
1114
1398
|
this.width = maxWidth;
|
|
1115
1399
|
this.buildLines(code);
|
|
1400
|
+
this.on("wheel", (e) => {
|
|
1401
|
+
const max = this.maxScrollX;
|
|
1402
|
+
if (max <= 0) return;
|
|
1403
|
+
if (e.ctrlKey === true) return;
|
|
1404
|
+
const deltaMode = e.deltaMode ?? 0;
|
|
1405
|
+
let deltaX = e.deltaX ?? 0;
|
|
1406
|
+
let deltaY = e.deltaY ?? 0;
|
|
1407
|
+
if (deltaMode === 1) {
|
|
1408
|
+
deltaX *= 16;
|
|
1409
|
+
deltaY *= 16;
|
|
1410
|
+
} else if (deltaMode === 2) {
|
|
1411
|
+
deltaX *= this.width;
|
|
1412
|
+
deltaY *= this.height;
|
|
1413
|
+
}
|
|
1414
|
+
const horizontal = e.shiftKey === true ? deltaY || deltaX : deltaX;
|
|
1415
|
+
if (horizontal === 0) return;
|
|
1416
|
+
const before = this.scrollX;
|
|
1417
|
+
this.setScrollX(before + horizontal);
|
|
1418
|
+
if (this.scrollX !== before) e.nativeEvent?.preventDefault?.();
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* The language name shown in the header, or `''` when there is nothing to show.
|
|
1423
|
+
*
|
|
1424
|
+
* Normalized exactly as the highlighter normalizes its lookup key, so the label
|
|
1425
|
+
* and the colouring can never disagree about which language this is: a fence
|
|
1426
|
+
* may be written ` ```Bash ` or carry attributes (` ```ts title="a.ts" `), and
|
|
1427
|
+
* the label has to be the language, not the raw info string.
|
|
1428
|
+
*
|
|
1429
|
+
* Lowercased for the same reason `streamdown` lowercases its own
|
|
1430
|
+
* (`lib/code-block/header.tsx:15`): the fence's capitalization is incidental,
|
|
1431
|
+
* and a document mixing ` ```JS ` with ` ```js ` should not render two
|
|
1432
|
+
* different-looking labels for one language.
|
|
1433
|
+
*/
|
|
1434
|
+
languageLabel() {
|
|
1435
|
+
return this.lang.trim().toLowerCase().split(/[\s:,{]/)[0] ?? "";
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Height in px of the header band, or `0` when it is off.
|
|
1439
|
+
*
|
|
1440
|
+
* The label sits in a band of its own rather than floating over the code,
|
|
1441
|
+
* because a translucent overlay above real glyphs is unreadable at small sizes
|
|
1442
|
+
* and would fight the horizontal scroll: the code slides under it, so any text
|
|
1443
|
+
* drawn on top would collide with a different token every frame.
|
|
1444
|
+
*/
|
|
1445
|
+
headerHeight() {
|
|
1446
|
+
if (!this.showLanguage) return 0;
|
|
1447
|
+
return this.theme.codeLangFontSize + Math.round(this.pad * 0.75);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Local y of the first line of code.
|
|
1451
|
+
*
|
|
1452
|
+
* Everything that positions a row — the painter, the projection, the grid's
|
|
1453
|
+
* own origin — goes through this, so the header offset cannot be applied to
|
|
1454
|
+
* one and forgotten on another. That class of mismatch is exactly what
|
|
1455
|
+
* detaches selection carriers from the glyphs they cover.
|
|
1456
|
+
*/
|
|
1457
|
+
contentTop() {
|
|
1458
|
+
return this.headerHeight() + this.pad;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Current horizontal scroll offset in local px, clamped to what the content
|
|
1462
|
+
* currently allows.
|
|
1463
|
+
*
|
|
1464
|
+
* Clamped on READ, not only on write, because `setWidth()` may shrink the box
|
|
1465
|
+
* after a scroll and is contractually forbidden from rebuilding anything. Both
|
|
1466
|
+
* the painter and the projection read through here, which is what keeps the
|
|
1467
|
+
* glyphs and the selection carriers on the same offset within a frame.
|
|
1468
|
+
*/
|
|
1469
|
+
get scrollX() {
|
|
1470
|
+
return Math.min(this.scrollXValue, this.maxScrollX);
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Widest line's overflow past the padded box, i.e. the maximum useful
|
|
1474
|
+
* {@link scrollX}. `0` when every line already fits.
|
|
1475
|
+
*/
|
|
1476
|
+
get maxScrollX() {
|
|
1477
|
+
return Math.max(0, this.contentWidth() - (this.width - this.pad * 2));
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Widest prepared line, memoized against the grid that produced it.
|
|
1481
|
+
*
|
|
1482
|
+
* Read by {@link scrollX}, which both `render()` and `getContentProjection()`
|
|
1483
|
+
* call every synced frame, so an O(lines) scan here would be an O(document) cost
|
|
1484
|
+
* per frame on a long block — the exact shape the per-line projection window
|
|
1485
|
+
* exists to avoid. The grid is rebuilt only when the content changes, so the
|
|
1486
|
+
* cache key is identity of the grid object.
|
|
1487
|
+
*/
|
|
1488
|
+
contentWidth() {
|
|
1489
|
+
const grid = this.ensureGrid();
|
|
1490
|
+
if (this.contentWidthGrid === grid) return this.contentWidthValue;
|
|
1491
|
+
let widest = 0;
|
|
1492
|
+
for (const line of grid.lines) {
|
|
1493
|
+
if (line.width > widest) widest = line.width;
|
|
1494
|
+
}
|
|
1495
|
+
this.contentWidthGrid = grid;
|
|
1496
|
+
this.contentWidthValue = widest;
|
|
1497
|
+
return widest;
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Scroll horizontally to `x`, clamped to `[0, maxScrollX]`.
|
|
1501
|
+
*
|
|
1502
|
+
* @returns `this` for chaining.
|
|
1503
|
+
*/
|
|
1504
|
+
setScrollX(x) {
|
|
1505
|
+
const next = Math.max(0, Math.min(this.maxScrollX, x));
|
|
1506
|
+
if (next === this.scrollXValue) return this;
|
|
1507
|
+
this.scrollXValue = next;
|
|
1508
|
+
this.contentEpoch++;
|
|
1509
|
+
this.scene?.markDirty();
|
|
1510
|
+
return this;
|
|
1116
1511
|
}
|
|
1117
1512
|
/** Re-parse code content (e.g. for live editing). */
|
|
1118
1513
|
setCode(code, lang) {
|
|
1119
1514
|
if (lang !== void 0) this.lang = lang;
|
|
1120
1515
|
this.source = code;
|
|
1121
1516
|
this.buildLines(code);
|
|
1517
|
+
this.scrollXValue = Math.min(this.scrollXValue, this.maxScrollX);
|
|
1122
1518
|
this.scene?.markDirty();
|
|
1123
1519
|
return this;
|
|
1124
1520
|
}
|
|
@@ -1138,9 +1534,13 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1138
1534
|
* Deliberately does **not** rebuild the grid or the highlight, because code does
|
|
1139
1535
|
* not reflow: lines are placed on a fixed monospace grid at `col × cellWidth` and
|
|
1140
1536
|
* a long line overflows rather than wrapping, so `height` is a function of line
|
|
1141
|
-
* *count* alone. The width
|
|
1142
|
-
* change the glyph geometry — the source, the language, the font —
|
|
1143
|
-
* {@link setCode} and invalidates the grid there.
|
|
1537
|
+
* *count* alone. The width sizes the rounded background and the clip. Anything
|
|
1538
|
+
* that would change the glyph geometry — the source, the language, the font —
|
|
1539
|
+
* goes through {@link setCode} and invalidates the grid there.
|
|
1540
|
+
*
|
|
1541
|
+
* A narrower box can leave {@link scrollX} past the new end of travel. That is
|
|
1542
|
+
* resolved by clamping on read rather than by adjusting anything here, so this
|
|
1543
|
+
* method keeps costing nothing.
|
|
1144
1544
|
*
|
|
1145
1545
|
* @returns `this` for chaining.
|
|
1146
1546
|
*/
|
|
@@ -1154,16 +1554,21 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1154
1554
|
getContentProjection(hint) {
|
|
1155
1555
|
if (!this.source) return null;
|
|
1156
1556
|
const grid = this.ensureGrid();
|
|
1557
|
+
const scrollX = this.scrollX;
|
|
1157
1558
|
const rows = [];
|
|
1158
1559
|
rows.length = grid.lines.length;
|
|
1159
1560
|
for (let row = 0; row < grid.lines.length; row++) {
|
|
1160
1561
|
const line = grid.lines[row];
|
|
1161
|
-
const y = this.
|
|
1562
|
+
const y = this.contentTop() + row * this.lineH;
|
|
1162
1563
|
if (!(0, import_core2.contentLineInHint)(hint, y, this.lineH)) continue;
|
|
1163
1564
|
rows[row] = {
|
|
1164
1565
|
text: this.source.slice(line.sourceStart, line.sourceEnd),
|
|
1165
1566
|
separatorAfter: this.source.slice(line.sourceEnd, line.nextSourceStart) || void 0,
|
|
1166
|
-
|
|
1567
|
+
// The SAME offset `render()` subtracts, read through the same clamping
|
|
1568
|
+
// accessor. Cell carriers are `position: relative` inside this `absolute`
|
|
1569
|
+
// line box, so shifting the line's x translates every cell of the line
|
|
1570
|
+
// rigidly and selection stays over the glyphs.
|
|
1571
|
+
x: this.pad - scrollX,
|
|
1167
1572
|
y,
|
|
1168
1573
|
baseline: this.lineH * 0.75,
|
|
1169
1574
|
font: this.codeFont,
|
|
@@ -1183,6 +1588,12 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1183
1588
|
// render() draws cell-by-cell (no ligatures can form); the DOM copy
|
|
1184
1589
|
// must not ligate either or Firefox selection geometry drifts.
|
|
1185
1590
|
ligatures: "none",
|
|
1591
|
+
// `render()` clips the glyph pass to this box, so the DOM copy must too.
|
|
1592
|
+
// A line wider than the box otherwise projects carriers past the entity,
|
|
1593
|
+
// and the browser paints their selection highlight over whatever is drawn
|
|
1594
|
+
// beside the block — measured 1580px of carrier against a 1566px viewport
|
|
1595
|
+
// on a real page, the highlight running through the prose to its right.
|
|
1596
|
+
clipToBounds: true,
|
|
1186
1597
|
grid
|
|
1187
1598
|
};
|
|
1188
1599
|
}
|
|
@@ -1197,6 +1608,11 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1197
1608
|
*
|
|
1198
1609
|
* The last previously-seen line is deliberately NOT reused: a chunk usually
|
|
1199
1610
|
* lands mid-line, so that line's text (and therefore its tokenization) changes.
|
|
1611
|
+
*
|
|
1612
|
+
* Prefix reuse survives multi-line constructs because {@link lineCarry} records
|
|
1613
|
+
* the state ENTERING each line, so resuming at the reuse boundary needs no
|
|
1614
|
+
* rescan: a carried state is a pure function of the preceding text, and that
|
|
1615
|
+
* text is byte-identical over the reused prefix by construction.
|
|
1200
1616
|
*/
|
|
1201
1617
|
buildLines(code) {
|
|
1202
1618
|
this.contentEpoch++;
|
|
@@ -1207,18 +1623,20 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1207
1623
|
const limit = Math.min(previous.length - 1, rawLines.length);
|
|
1208
1624
|
while (reusable < limit && previous[reusable] === rawLines[reusable]) reusable++;
|
|
1209
1625
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
this.
|
|
1216
|
-
|
|
1217
|
-
|
|
1626
|
+
const lines = reusable > 0 ? this.lines.slice(0, reusable) : [];
|
|
1627
|
+
const carries = reusable > 0 ? this.lineCarry.slice(0, reusable) : [];
|
|
1628
|
+
let carry = reusable > 0 ? this.lineCarry[reusable] ?? null : null;
|
|
1629
|
+
for (let i = reusable; i < rawLines.length; i++) {
|
|
1630
|
+
carries.push(carry);
|
|
1631
|
+
const result = highlightLine(rawLines[i], this.lang, this.theme, carry);
|
|
1632
|
+
lines.push(result.segments);
|
|
1633
|
+
carry = result.carry;
|
|
1218
1634
|
}
|
|
1635
|
+
this.lines = lines;
|
|
1636
|
+
this.lineCarry = carries;
|
|
1219
1637
|
this.rawLines = rawLines;
|
|
1220
1638
|
this.grid = null;
|
|
1221
|
-
this.height = this.
|
|
1639
|
+
this.height = this.contentTop() + this.pad + rawLines.length * this.lineH;
|
|
1222
1640
|
}
|
|
1223
1641
|
ensureGrid() {
|
|
1224
1642
|
const cellWidth = this.cellWidth || Math.max(1, (0, import_ui.measureText)("M", this.codeFont));
|
|
@@ -1232,7 +1650,15 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1232
1650
|
}
|
|
1233
1651
|
return this.grid;
|
|
1234
1652
|
}
|
|
1235
|
-
/**
|
|
1653
|
+
/**
|
|
1654
|
+
* Not hit-testable, and deliberately still not `interactive`, even though the
|
|
1655
|
+
* block now consumes wheel events to scroll.
|
|
1656
|
+
*
|
|
1657
|
+
* The wheel arrives from the content-projection div rather than from canvas
|
|
1658
|
+
* hit-testing, so no a11y shadow node is needed. Creating one would place a
|
|
1659
|
+
* `pointer-events: auto` element above the transparent text mirror and swallow
|
|
1660
|
+
* the mousedown that starts a native drag-selection.
|
|
1661
|
+
*/
|
|
1236
1662
|
isPointInside() {
|
|
1237
1663
|
return false;
|
|
1238
1664
|
}
|
|
@@ -1244,8 +1670,25 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1244
1670
|
const atlas = codeGlyphAtlas(r);
|
|
1245
1671
|
const atlasSource = atlas?.source ?? null;
|
|
1246
1672
|
const blit = atlas ? r.drawImageRect : void 0;
|
|
1673
|
+
const header = this.headerHeight();
|
|
1674
|
+
if (header > 0) {
|
|
1675
|
+
r.fillText(
|
|
1676
|
+
this.languageLabel(),
|
|
1677
|
+
this.pad,
|
|
1678
|
+
// Vertically centred in the band by its own cap height rather than by
|
|
1679
|
+
// font size: `fillText` takes a baseline, so centring the em box would
|
|
1680
|
+
// sit the visible letterforms low. 0.7 of the label size below the band's
|
|
1681
|
+
// centre line is where a lowercase-plus-cap run reads as centred.
|
|
1682
|
+
(header + this.theme.codeLangFontSize * 0.7) / 2,
|
|
1683
|
+
this.langFont,
|
|
1684
|
+
this.theme.codeLangColor
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
r.save();
|
|
1688
|
+
r.clip(0, header, this.width, this.height - header);
|
|
1689
|
+
const scrollX = this.scrollX;
|
|
1247
1690
|
for (let row = 0; row < grid.lines.length; row++) {
|
|
1248
|
-
const yBaseline = this.
|
|
1691
|
+
const yBaseline = this.contentTop() + row * this.lineH + this.lineH * 0.75;
|
|
1249
1692
|
const segments = this.lines[row];
|
|
1250
1693
|
let segmentIndex = 0;
|
|
1251
1694
|
let segmentEnd = segments[0]?.text.length ?? 0;
|
|
@@ -1259,7 +1702,8 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1259
1702
|
const sourceText = this.source.slice(cell.sourceStart, cell.sourceEnd);
|
|
1260
1703
|
if (cell.advance <= 0 || sourceText === " " || sourceText === " ") continue;
|
|
1261
1704
|
const color = segments[segmentIndex]?.color ?? this.theme.codeColor;
|
|
1262
|
-
const x = this.pad + cell.x;
|
|
1705
|
+
const x = this.pad + cell.x - scrollX;
|
|
1706
|
+
if (x + cell.advance < 0 || x > this.width) continue;
|
|
1263
1707
|
if (blit && atlas) {
|
|
1264
1708
|
const slot = atlas.get(this.codeFont, color, cell.glyph);
|
|
1265
1709
|
const src = atlasSource ?? atlas.source;
|
|
@@ -1282,6 +1726,7 @@ var CodeBlock = class extends import_ui.UIComponent {
|
|
|
1282
1726
|
r.fillText(cell.glyph, x, yBaseline, this.codeFont, color);
|
|
1283
1727
|
}
|
|
1284
1728
|
}
|
|
1729
|
+
r.restore();
|
|
1285
1730
|
}
|
|
1286
1731
|
};
|
|
1287
1732
|
var codeAtlases = /* @__PURE__ */ new Map();
|
|
@@ -2533,6 +2978,20 @@ function defaultSaveFile(filename, content, mimeType) {
|
|
|
2533
2978
|
doc.body.removeChild(anchor);
|
|
2534
2979
|
URL.revokeObjectURL(url);
|
|
2535
2980
|
}
|
|
2981
|
+
function resolveBlockAffordanceConfig(config = {}) {
|
|
2982
|
+
return {
|
|
2983
|
+
copy: config.copy ?? true,
|
|
2984
|
+
download: config.download ?? true,
|
|
2985
|
+
labels: {
|
|
2986
|
+
copyCode: config.labels?.copyCode ?? "Copy code",
|
|
2987
|
+
downloadCode: config.labels?.downloadCode ?? "Download code",
|
|
2988
|
+
copyTable: config.labels?.copyTable ?? "Copy table",
|
|
2989
|
+
downloadTable: config.labels?.downloadTable ?? "Download table",
|
|
2990
|
+
copied: config.labels?.copied ?? "Copied",
|
|
2991
|
+
saved: config.labels?.saved ?? "Saved"
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2536
2995
|
var BlockAffordanceButton = class _BlockAffordanceButton extends import_ui3.Button {
|
|
2537
2996
|
constructor(label, successLabel, act, opts = {}) {
|
|
2538
2997
|
super(label, { ...opts, onClick: () => this.run() });
|
|
@@ -2854,6 +3313,22 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
|
|
|
2854
3313
|
* affordance.
|
|
2855
3314
|
*/
|
|
2856
3315
|
blockAffordances;
|
|
3316
|
+
/**
|
|
3317
|
+
* Which controls a block carries and what they are called, with defaults
|
|
3318
|
+
* applied.
|
|
3319
|
+
*
|
|
3320
|
+
* Resolved once in the constructor rather than per block: the defaults are
|
|
3321
|
+
* fixed, and re-deriving them for every code fence in a long document would
|
|
3322
|
+
* repeat the same six `??` fallbacks for no benefit.
|
|
3323
|
+
*/
|
|
3324
|
+
affordanceConfig;
|
|
3325
|
+
/**
|
|
3326
|
+
* Whether code blocks show their language in a header band.
|
|
3327
|
+
*
|
|
3328
|
+
* Read when a block entity is built, exactly like {@link blockAffordances}, so
|
|
3329
|
+
* it affects blocks rendered from here on rather than retroactively.
|
|
3330
|
+
*/
|
|
3331
|
+
showCodeLanguage;
|
|
2857
3332
|
/** Clipboard writer used by the copy controls. */
|
|
2858
3333
|
writeClipboard;
|
|
2859
3334
|
/** File saver used by the download controls. */
|
|
@@ -3136,6 +3611,8 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
|
|
|
3136
3611
|
this.selectable = opts.selectable ?? true;
|
|
3137
3612
|
this._userTiming = opts.userTiming ?? false;
|
|
3138
3613
|
this.blockAffordances = opts.blockAffordances ?? false;
|
|
3614
|
+
this.affordanceConfig = resolveBlockAffordanceConfig(opts.affordances);
|
|
3615
|
+
this.showCodeLanguage = opts.showCodeLanguage ?? false;
|
|
3139
3616
|
this.writeClipboard = opts.writeClipboard ?? defaultWriteClipboard;
|
|
3140
3617
|
this.saveFile = opts.saveFile ?? defaultSaveFile;
|
|
3141
3618
|
this.content = new import_ui4.Stack({
|
|
@@ -4097,40 +4574,60 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
|
|
|
4097
4574
|
const controls = make();
|
|
4098
4575
|
return controls.length > 0 ? new BlockWithAffordances(block, controls) : block;
|
|
4099
4576
|
}
|
|
4100
|
-
/** Copy and download controls for one fenced code block. */
|
|
4577
|
+
/** Copy and download controls for one fenced code block, per {@link affordanceConfig}. */
|
|
4101
4578
|
codeBlockAffordances(source, lang) {
|
|
4102
4579
|
const opts = this.affordanceButtonOptions();
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4580
|
+
const { copy, download, labels } = this.affordanceConfig;
|
|
4581
|
+
const controls = [];
|
|
4582
|
+
if (copy) {
|
|
4583
|
+
controls.push(
|
|
4584
|
+
new BlockAffordanceButton(
|
|
4585
|
+
labels.copyCode,
|
|
4586
|
+
labels.copied,
|
|
4587
|
+
() => this.writeClipboard(source),
|
|
4588
|
+
opts
|
|
4589
|
+
)
|
|
4590
|
+
);
|
|
4591
|
+
}
|
|
4592
|
+
if (download) {
|
|
4593
|
+
controls.push(
|
|
4594
|
+
new BlockAffordanceButton(
|
|
4595
|
+
labels.downloadCode,
|
|
4596
|
+
labels.saved,
|
|
4597
|
+
() => this.saveFile(`code.${extensionForLanguage(lang)}`, source, mimeForLanguage(lang)),
|
|
4598
|
+
opts
|
|
4599
|
+
)
|
|
4600
|
+
);
|
|
4601
|
+
}
|
|
4602
|
+
return controls;
|
|
4603
|
+
}
|
|
4604
|
+
/** Copy (as Markdown) and download (as CSV) controls for one table, per {@link affordanceConfig}. */
|
|
4114
4605
|
tableAffordances(tblToken) {
|
|
4606
|
+
const { copy, download, labels } = this.affordanceConfig;
|
|
4115
4607
|
const content = tableContentOf(tblToken);
|
|
4116
4608
|
const opts = this.affordanceButtonOptions();
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
)
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4609
|
+
const controls = [];
|
|
4610
|
+
if (copy) {
|
|
4611
|
+
controls.push(
|
|
4612
|
+
new BlockAffordanceButton(
|
|
4613
|
+
labels.copyTable,
|
|
4614
|
+
labels.copied,
|
|
4615
|
+
() => this.writeClipboard(tableToMarkdown(content)),
|
|
4616
|
+
opts
|
|
4617
|
+
)
|
|
4618
|
+
);
|
|
4619
|
+
}
|
|
4620
|
+
if (download) {
|
|
4621
|
+
controls.push(
|
|
4622
|
+
new BlockAffordanceButton(
|
|
4623
|
+
labels.downloadTable,
|
|
4624
|
+
labels.saved,
|
|
4625
|
+
() => this.saveFile("table.csv", tableToCsv(content), "text/csv;charset=utf-8"),
|
|
4626
|
+
opts
|
|
4627
|
+
)
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
return controls;
|
|
4134
4631
|
}
|
|
4135
4632
|
/**
|
|
4136
4633
|
* Button styling for the affordances, derived from the document theme.
|
|
@@ -5274,7 +5771,9 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
|
|
|
5274
5771
|
}
|
|
5275
5772
|
}
|
|
5276
5773
|
return this.withBlockAffordances(
|
|
5277
|
-
new CodeBlock(codeToken.text, lang, availableWidth, t, this.selectable
|
|
5774
|
+
new CodeBlock(codeToken.text, lang, availableWidth, t, this.selectable, {
|
|
5775
|
+
showLanguage: this.showCodeLanguage
|
|
5776
|
+
}),
|
|
5278
5777
|
() => this.codeBlockAffordances(codeToken.text, lang)
|
|
5279
5778
|
);
|
|
5280
5779
|
}
|
|
@@ -5506,6 +6005,7 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
|
|
|
5506
6005
|
extensionForLanguage,
|
|
5507
6006
|
footnoteMarker,
|
|
5508
6007
|
hasFencedBlockRenderer,
|
|
6008
|
+
highlightedLanguages,
|
|
5509
6009
|
isFencedBlockRendererReady,
|
|
5510
6010
|
isMathJaxReady,
|
|
5511
6011
|
isPresetName,
|
|
@@ -5514,6 +6014,7 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
|
|
|
5514
6014
|
preloadMathJax,
|
|
5515
6015
|
registerFencedBlockRenderer,
|
|
5516
6016
|
renderFencedBlock,
|
|
6017
|
+
resolveBlockAffordanceConfig,
|
|
5517
6018
|
resolvePresetTheme,
|
|
5518
6019
|
scanFrontMatter,
|
|
5519
6020
|
tableContentOf,
|