@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.mjs
CHANGED
|
@@ -554,11 +554,13 @@ var DEFAULT_THEME = {
|
|
|
554
554
|
syntaxStringColor: "#86efac",
|
|
555
555
|
syntaxCommentColor: "#64748b",
|
|
556
556
|
syntaxNumberColor: "#fbbf24",
|
|
557
|
+
codeLangColor: "#64748b",
|
|
557
558
|
bodyFont: "Inter, system-ui, sans-serif",
|
|
558
559
|
codeFont: 'ui-monospace, "JetBrains Mono", "Fira Code", monospace',
|
|
559
560
|
fontSize: 16,
|
|
560
561
|
headingSizes: [32, 28, 24, 20, 18, 16],
|
|
561
562
|
codeFontSize: 15,
|
|
563
|
+
codeLangFontSize: 12,
|
|
562
564
|
tableFontSize: 14,
|
|
563
565
|
footnoteMarkerScale: 0.75,
|
|
564
566
|
subscriptScale: 0.75,
|
|
@@ -593,6 +595,12 @@ function resolveTheme(theme) {
|
|
|
593
595
|
if (theme?.footnoteColor === void 0) {
|
|
594
596
|
merged.footnoteColor = merged.linkColor;
|
|
595
597
|
}
|
|
598
|
+
if (theme?.codeLangColor === void 0) {
|
|
599
|
+
merged.codeLangColor = merged.syntaxCommentColor;
|
|
600
|
+
}
|
|
601
|
+
if (theme?.codeLangFontSize === void 0) {
|
|
602
|
+
merged.codeLangFontSize = Math.max(1, merged.codeFontSize - 3);
|
|
603
|
+
}
|
|
596
604
|
return merged;
|
|
597
605
|
}
|
|
598
606
|
function headingSize(theme, depth) {
|
|
@@ -932,14 +940,215 @@ var KEYWORD_SETS = {
|
|
|
932
940
|
"static"
|
|
933
941
|
])
|
|
934
942
|
};
|
|
943
|
+
KEYWORD_SETS["bash"] = /* @__PURE__ */ new Set([
|
|
944
|
+
// Shell builtins and control words. Deliberately not the whole of coreutils:
|
|
945
|
+
// a keyword table that includes every command name colors an entire script
|
|
946
|
+
// uniformly, which reads worse than coloring only the control flow.
|
|
947
|
+
"if",
|
|
948
|
+
"then",
|
|
949
|
+
"else",
|
|
950
|
+
"elif",
|
|
951
|
+
"fi",
|
|
952
|
+
"for",
|
|
953
|
+
"while",
|
|
954
|
+
"until",
|
|
955
|
+
"do",
|
|
956
|
+
"done",
|
|
957
|
+
"case",
|
|
958
|
+
"esac",
|
|
959
|
+
"in",
|
|
960
|
+
"function",
|
|
961
|
+
"return",
|
|
962
|
+
"exit",
|
|
963
|
+
"break",
|
|
964
|
+
"continue",
|
|
965
|
+
"local",
|
|
966
|
+
"export",
|
|
967
|
+
"readonly",
|
|
968
|
+
"declare",
|
|
969
|
+
"unset",
|
|
970
|
+
"shift",
|
|
971
|
+
"source",
|
|
972
|
+
"alias",
|
|
973
|
+
"set",
|
|
974
|
+
"trap",
|
|
975
|
+
"echo",
|
|
976
|
+
"cd",
|
|
977
|
+
"sudo",
|
|
978
|
+
"true",
|
|
979
|
+
"false"
|
|
980
|
+
]);
|
|
981
|
+
KEYWORD_SETS["json"] = /* @__PURE__ */ new Set(["true", "false", "null"]);
|
|
982
|
+
KEYWORD_SETS["css"] = /* @__PURE__ */ new Set([
|
|
983
|
+
"important",
|
|
984
|
+
"inherit",
|
|
985
|
+
"initial",
|
|
986
|
+
"unset",
|
|
987
|
+
"revert",
|
|
988
|
+
"auto",
|
|
989
|
+
"none",
|
|
990
|
+
"var",
|
|
991
|
+
"calc"
|
|
992
|
+
]);
|
|
993
|
+
KEYWORD_SETS["html"] = /* @__PURE__ */ new Set([
|
|
994
|
+
// Tag names are the meaningful tokens a reader scans for. The tokenizer is
|
|
995
|
+
// word-based, so `<div>` yields the word `div`.
|
|
996
|
+
"html",
|
|
997
|
+
"head",
|
|
998
|
+
"body",
|
|
999
|
+
"title",
|
|
1000
|
+
"meta",
|
|
1001
|
+
"link",
|
|
1002
|
+
"script",
|
|
1003
|
+
"style",
|
|
1004
|
+
"div",
|
|
1005
|
+
"span",
|
|
1006
|
+
"p",
|
|
1007
|
+
"a",
|
|
1008
|
+
"img",
|
|
1009
|
+
"ul",
|
|
1010
|
+
"ol",
|
|
1011
|
+
"li",
|
|
1012
|
+
"table",
|
|
1013
|
+
"tr",
|
|
1014
|
+
"td",
|
|
1015
|
+
"th",
|
|
1016
|
+
"form",
|
|
1017
|
+
"input",
|
|
1018
|
+
"button",
|
|
1019
|
+
"label",
|
|
1020
|
+
"select",
|
|
1021
|
+
"option",
|
|
1022
|
+
"textarea",
|
|
1023
|
+
"header",
|
|
1024
|
+
"footer",
|
|
1025
|
+
"nav",
|
|
1026
|
+
"main",
|
|
1027
|
+
"section",
|
|
1028
|
+
"article",
|
|
1029
|
+
"aside",
|
|
1030
|
+
"canvas",
|
|
1031
|
+
"svg",
|
|
1032
|
+
"template",
|
|
1033
|
+
"slot"
|
|
1034
|
+
]);
|
|
935
1035
|
KEYWORD_SETS["javascript"] = KEYWORD_SETS["js"];
|
|
936
1036
|
KEYWORD_SETS["typescript"] = KEYWORD_SETS["ts"];
|
|
937
1037
|
KEYWORD_SETS["python"] = KEYWORD_SETS["py"];
|
|
938
1038
|
KEYWORD_SETS["rs"] = KEYWORD_SETS["rust"];
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1039
|
+
KEYWORD_SETS["jsx"] = KEYWORD_SETS["js"];
|
|
1040
|
+
KEYWORD_SETS["mjs"] = KEYWORD_SETS["js"];
|
|
1041
|
+
KEYWORD_SETS["cjs"] = KEYWORD_SETS["js"];
|
|
1042
|
+
KEYWORD_SETS["tsx"] = KEYWORD_SETS["ts"];
|
|
1043
|
+
KEYWORD_SETS["mts"] = KEYWORD_SETS["ts"];
|
|
1044
|
+
KEYWORD_SETS["cts"] = KEYWORD_SETS["ts"];
|
|
1045
|
+
KEYWORD_SETS["sh"] = KEYWORD_SETS["bash"];
|
|
1046
|
+
KEYWORD_SETS["zsh"] = KEYWORD_SETS["bash"];
|
|
1047
|
+
KEYWORD_SETS["shell"] = KEYWORD_SETS["bash"];
|
|
1048
|
+
KEYWORD_SETS["console"] = KEYWORD_SETS["bash"];
|
|
1049
|
+
KEYWORD_SETS["jsonc"] = KEYWORD_SETS["json"];
|
|
1050
|
+
KEYWORD_SETS["json5"] = KEYWORD_SETS["json"];
|
|
1051
|
+
KEYWORD_SETS["scss"] = KEYWORD_SETS["css"];
|
|
1052
|
+
KEYWORD_SETS["sass"] = KEYWORD_SETS["css"];
|
|
1053
|
+
KEYWORD_SETS["less"] = KEYWORD_SETS["css"];
|
|
1054
|
+
KEYWORD_SETS["vue"] = KEYWORD_SETS["html"];
|
|
1055
|
+
KEYWORD_SETS["svelte"] = KEYWORD_SETS["html"];
|
|
1056
|
+
KEYWORD_SETS["xml"] = KEYWORD_SETS["html"];
|
|
1057
|
+
KEYWORD_SETS["svg"] = KEYWORD_SETS["html"];
|
|
1058
|
+
var C_LIKE = {
|
|
1059
|
+
lineComments: ["//"],
|
|
1060
|
+
quotes: ['"', "'", "`"],
|
|
1061
|
+
numbers: true,
|
|
1062
|
+
blockComments: [["/*", "*/"]],
|
|
1063
|
+
// A JS/TS template literal spans lines. Listed here as well as in `quotes`:
|
|
1064
|
+
// `quotes` handles the common single-line case, and this carries the rest.
|
|
1065
|
+
multilineStrings: ["`"]
|
|
1066
|
+
};
|
|
1067
|
+
var HASH_COMMENT = {
|
|
1068
|
+
lineComments: ["#"],
|
|
1069
|
+
quotes: ['"', "'"],
|
|
1070
|
+
numbers: true
|
|
1071
|
+
};
|
|
1072
|
+
var LANGUAGE_SYNTAX = {
|
|
1073
|
+
js: C_LIKE,
|
|
1074
|
+
ts: C_LIKE,
|
|
1075
|
+
// A Python docstring is the language's block comment in practice, and it is
|
|
1076
|
+
// lexically a string, so it is carried as one rather than invented as a third
|
|
1077
|
+
// kind. Triple delimiters are listed before the single ones so the longest
|
|
1078
|
+
// match wins.
|
|
1079
|
+
py: { ...HASH_COMMENT, multilineStrings: ['"""', "'''"] },
|
|
1080
|
+
// Rust has `//` line comments AND `'` lifetimes. The unterminated-quote
|
|
1081
|
+
// fallback already keeps a lifetime from swallowing the line, so `'` stays
|
|
1082
|
+
// listed: `'a'` is a valid char literal and should color as a string.
|
|
1083
|
+
rust: C_LIKE,
|
|
1084
|
+
bash: HASH_COMMENT,
|
|
1085
|
+
// JSON has no comments and no single-quoted strings. JSONC does have `//`,
|
|
1086
|
+
// and is aliased separately below rather than sharing this entry.
|
|
1087
|
+
json: { lineComments: [], quotes: ['"'], numbers: true },
|
|
1088
|
+
// CSS has only block comments — which now span lines, so this entry claims
|
|
1089
|
+
// them. Numbers are everywhere in CSS and coloring them is most of the visible
|
|
1090
|
+
// benefit.
|
|
1091
|
+
css: {
|
|
1092
|
+
lineComments: [],
|
|
1093
|
+
quotes: ['"', "'"],
|
|
1094
|
+
numbers: true,
|
|
1095
|
+
blockComments: [["/*", "*/"]]
|
|
1096
|
+
},
|
|
1097
|
+
// Markup: no line comments, and numbers inside attribute values are noise
|
|
1098
|
+
// rather than signal. An SGML comment spans lines like any other block form.
|
|
1099
|
+
html: {
|
|
1100
|
+
lineComments: [],
|
|
1101
|
+
quotes: ['"', "'"],
|
|
1102
|
+
numbers: false,
|
|
1103
|
+
blockComments: [["<!--", "-->"]]
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
LANGUAGE_SYNTAX["javascript"] = LANGUAGE_SYNTAX["js"];
|
|
1107
|
+
LANGUAGE_SYNTAX["typescript"] = LANGUAGE_SYNTAX["ts"];
|
|
1108
|
+
LANGUAGE_SYNTAX["python"] = LANGUAGE_SYNTAX["py"];
|
|
1109
|
+
LANGUAGE_SYNTAX["rs"] = LANGUAGE_SYNTAX["rust"];
|
|
1110
|
+
LANGUAGE_SYNTAX["jsx"] = LANGUAGE_SYNTAX["js"];
|
|
1111
|
+
LANGUAGE_SYNTAX["mjs"] = LANGUAGE_SYNTAX["js"];
|
|
1112
|
+
LANGUAGE_SYNTAX["cjs"] = LANGUAGE_SYNTAX["js"];
|
|
1113
|
+
LANGUAGE_SYNTAX["tsx"] = LANGUAGE_SYNTAX["ts"];
|
|
1114
|
+
LANGUAGE_SYNTAX["mts"] = LANGUAGE_SYNTAX["ts"];
|
|
1115
|
+
LANGUAGE_SYNTAX["cts"] = LANGUAGE_SYNTAX["ts"];
|
|
1116
|
+
LANGUAGE_SYNTAX["sh"] = LANGUAGE_SYNTAX["bash"];
|
|
1117
|
+
LANGUAGE_SYNTAX["zsh"] = LANGUAGE_SYNTAX["bash"];
|
|
1118
|
+
LANGUAGE_SYNTAX["shell"] = LANGUAGE_SYNTAX["bash"];
|
|
1119
|
+
LANGUAGE_SYNTAX["console"] = LANGUAGE_SYNTAX["bash"];
|
|
1120
|
+
LANGUAGE_SYNTAX["yaml"] = HASH_COMMENT;
|
|
1121
|
+
LANGUAGE_SYNTAX["yml"] = HASH_COMMENT;
|
|
1122
|
+
LANGUAGE_SYNTAX["toml"] = HASH_COMMENT;
|
|
1123
|
+
LANGUAGE_SYNTAX["ini"] = HASH_COMMENT;
|
|
1124
|
+
LANGUAGE_SYNTAX["dockerfile"] = HASH_COMMENT;
|
|
1125
|
+
LANGUAGE_SYNTAX["makefile"] = HASH_COMMENT;
|
|
1126
|
+
LANGUAGE_SYNTAX["make"] = HASH_COMMENT;
|
|
1127
|
+
LANGUAGE_SYNTAX["jsonc"] = { lineComments: ["//"], quotes: ['"'], numbers: true };
|
|
1128
|
+
LANGUAGE_SYNTAX["json5"] = { lineComments: ["//"], quotes: ['"', "'"], numbers: true };
|
|
1129
|
+
LANGUAGE_SYNTAX["scss"] = C_LIKE;
|
|
1130
|
+
LANGUAGE_SYNTAX["sass"] = C_LIKE;
|
|
1131
|
+
LANGUAGE_SYNTAX["less"] = C_LIKE;
|
|
1132
|
+
LANGUAGE_SYNTAX["glsl"] = C_LIKE;
|
|
1133
|
+
LANGUAGE_SYNTAX["c"] = C_LIKE;
|
|
1134
|
+
LANGUAGE_SYNTAX["cpp"] = C_LIKE;
|
|
1135
|
+
LANGUAGE_SYNTAX["go"] = C_LIKE;
|
|
1136
|
+
LANGUAGE_SYNTAX["java"] = C_LIKE;
|
|
1137
|
+
LANGUAGE_SYNTAX["kotlin"] = C_LIKE;
|
|
1138
|
+
LANGUAGE_SYNTAX["swift"] = C_LIKE;
|
|
1139
|
+
LANGUAGE_SYNTAX["vue"] = LANGUAGE_SYNTAX["html"];
|
|
1140
|
+
LANGUAGE_SYNTAX["svelte"] = LANGUAGE_SYNTAX["html"];
|
|
1141
|
+
LANGUAGE_SYNTAX["xml"] = LANGUAGE_SYNTAX["html"];
|
|
1142
|
+
LANGUAGE_SYNTAX["svg"] = LANGUAGE_SYNTAX["html"];
|
|
1143
|
+
function highlightedLanguages() {
|
|
1144
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_SYNTAX), ...Object.keys(KEYWORD_SETS)])].sort();
|
|
1145
|
+
}
|
|
1146
|
+
function highlightLine(line, lang, theme, carry = null) {
|
|
1147
|
+
const key = lang.trim().toLowerCase().split(/[\s:,{]/)[0] ?? "";
|
|
1148
|
+
const keywords = KEYWORD_SETS[key];
|
|
1149
|
+
const syntax = LANGUAGE_SYNTAX[key];
|
|
1150
|
+
if (!keywords && !syntax) {
|
|
1151
|
+
return { segments: [{ text: line, color: theme.codeColor }], carry: null };
|
|
943
1152
|
}
|
|
944
1153
|
const segments = [];
|
|
945
1154
|
const KEYWORD_COLOR = theme.syntaxKeywordColor;
|
|
@@ -954,19 +1163,63 @@ function highlightLine(line, lang, theme) {
|
|
|
954
1163
|
buf = "";
|
|
955
1164
|
}
|
|
956
1165
|
};
|
|
1166
|
+
const lexical = syntax ?? C_LIKE;
|
|
1167
|
+
const findClose = (from, close, isString) => {
|
|
1168
|
+
let j = from;
|
|
1169
|
+
while (j < line.length) {
|
|
1170
|
+
if (isString && line[j] === "\\") {
|
|
1171
|
+
j += 2;
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
if (line.startsWith(close, j)) return j + close.length;
|
|
1175
|
+
j++;
|
|
1176
|
+
}
|
|
1177
|
+
return -1;
|
|
1178
|
+
};
|
|
1179
|
+
if (carry) {
|
|
1180
|
+
const color = carry.kind === "comment" ? COMMENT_COLOR : STRING_COLOR;
|
|
1181
|
+
const end = findClose(0, carry.close, carry.kind === "string");
|
|
1182
|
+
if (end === -1) {
|
|
1183
|
+
if (line.length > 0) segments.push({ text: line, color });
|
|
1184
|
+
return { segments, carry };
|
|
1185
|
+
}
|
|
1186
|
+
segments.push({ text: line.slice(0, end), color });
|
|
1187
|
+
i = end;
|
|
1188
|
+
}
|
|
957
1189
|
while (i < line.length) {
|
|
958
1190
|
const ch = line[i];
|
|
959
|
-
|
|
1191
|
+
const block = lexical.blockComments?.find(([open]) => line.startsWith(open, i));
|
|
1192
|
+
if (block) {
|
|
1193
|
+
const [open, close] = block;
|
|
960
1194
|
flush(theme.codeColor);
|
|
961
|
-
|
|
962
|
-
|
|
1195
|
+
const end = findClose(i + open.length, close, false);
|
|
1196
|
+
if (end === -1) {
|
|
1197
|
+
segments.push({ text: line.slice(i), color: COMMENT_COLOR });
|
|
1198
|
+
return { segments, carry: { kind: "comment", close } };
|
|
1199
|
+
}
|
|
1200
|
+
segments.push({ text: line.slice(i, end), color: COMMENT_COLOR });
|
|
1201
|
+
i = end;
|
|
1202
|
+
continue;
|
|
963
1203
|
}
|
|
964
|
-
|
|
1204
|
+
const comment = lexical.lineComments.find((prefix) => line.startsWith(prefix, i));
|
|
1205
|
+
if (comment !== void 0) {
|
|
965
1206
|
flush(theme.codeColor);
|
|
966
1207
|
segments.push({ text: line.slice(i), color: COMMENT_COLOR });
|
|
967
|
-
return segments;
|
|
1208
|
+
return { segments, carry: null };
|
|
968
1209
|
}
|
|
969
|
-
|
|
1210
|
+
const multi = lexical.multilineStrings?.find((delim) => line.startsWith(delim, i));
|
|
1211
|
+
if (multi !== void 0) {
|
|
1212
|
+
flush(theme.codeColor);
|
|
1213
|
+
const end = findClose(i + multi.length, multi, true);
|
|
1214
|
+
if (end === -1) {
|
|
1215
|
+
segments.push({ text: line.slice(i), color: STRING_COLOR });
|
|
1216
|
+
return { segments, carry: { kind: "string", close: multi } };
|
|
1217
|
+
}
|
|
1218
|
+
segments.push({ text: line.slice(i, end), color: STRING_COLOR });
|
|
1219
|
+
i = end;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (lexical.quotes.includes(ch)) {
|
|
970
1223
|
const quote = ch;
|
|
971
1224
|
let j = i + 1;
|
|
972
1225
|
let closed = false;
|
|
@@ -991,7 +1244,7 @@ function highlightLine(line, lang, theme) {
|
|
|
991
1244
|
i++;
|
|
992
1245
|
continue;
|
|
993
1246
|
}
|
|
994
|
-
if (/\d/.test(ch) && (i === 0 || /[\s(,=+\-*/<>[\]{}:;]/.test(line[i - 1]))) {
|
|
1247
|
+
if (lexical.numbers && /\d/.test(ch) && (i === 0 || /[\s(,=+\-*/<>[\]{}:;]/.test(line[i - 1]))) {
|
|
995
1248
|
flush(theme.codeColor);
|
|
996
1249
|
let j = i;
|
|
997
1250
|
while (j < line.length && /[\d._xXa-fA-F]/.test(line[j])) j++;
|
|
@@ -1006,7 +1259,9 @@ function highlightLine(line, lang, theme) {
|
|
|
1006
1259
|
const word = line.slice(i, j);
|
|
1007
1260
|
segments.push({
|
|
1008
1261
|
text: word,
|
|
1009
|
-
|
|
1262
|
+
// A language may have lexical syntax but no keywords (plain YAML, TOML,
|
|
1263
|
+
// a Dockerfile). Those still get comments, strings and numbers.
|
|
1264
|
+
color: keywords?.has(word) ? KEYWORD_COLOR : theme.codeColor
|
|
1010
1265
|
});
|
|
1011
1266
|
i = j;
|
|
1012
1267
|
continue;
|
|
@@ -1015,10 +1270,18 @@ function highlightLine(line, lang, theme) {
|
|
|
1015
1270
|
i++;
|
|
1016
1271
|
}
|
|
1017
1272
|
flush(theme.codeColor);
|
|
1018
|
-
return segments;
|
|
1273
|
+
return { segments, carry: null };
|
|
1019
1274
|
}
|
|
1020
1275
|
var CodeBlock = class extends UIComponent {
|
|
1021
1276
|
lines;
|
|
1277
|
+
/**
|
|
1278
|
+
* Lexical state ENTERING each line, index-aligned with {@link lines}.
|
|
1279
|
+
*
|
|
1280
|
+
* Entering rather than leaving, so a streamed append can resume tokenizing at
|
|
1281
|
+
* the prefix-reuse boundary by reading one entry instead of re-scanning the
|
|
1282
|
+
* document for an unclosed block comment.
|
|
1283
|
+
*/
|
|
1284
|
+
lineCarry = [];
|
|
1022
1285
|
grid = null;
|
|
1023
1286
|
/** Raw (unhighlighted) lines of the last build, for prefix reuse in buildLines. */
|
|
1024
1287
|
rawLines = null;
|
|
@@ -1026,6 +1289,19 @@ var CodeBlock = class extends UIComponent {
|
|
|
1026
1289
|
source;
|
|
1027
1290
|
/** Bumped by {@link buildLines} and {@link setSelectable}; read by `Scene`. */
|
|
1028
1291
|
contentEpoch = 0;
|
|
1292
|
+
/**
|
|
1293
|
+
* Horizontal scroll offset in local px, always in `[0, maxScrollX]`.
|
|
1294
|
+
*
|
|
1295
|
+
* Code does not wrap, so a line wider than the box would otherwise have an
|
|
1296
|
+
* unreachable tail. This offset is subtracted from BOTH the painted cell x and
|
|
1297
|
+
* the projected line x in the same frame — never one without the other, or the
|
|
1298
|
+
* DOM selection carriers detach from the glyphs they are supposed to cover
|
|
1299
|
+
* (the defect class `5cf7119` and `ee1de6f` fixed on the vertical axis).
|
|
1300
|
+
*/
|
|
1301
|
+
scrollXValue = 0;
|
|
1302
|
+
/** Memoized widest prepared line, keyed by the grid identity it came from. */
|
|
1303
|
+
contentWidthGrid = null;
|
|
1304
|
+
contentWidthValue = 0;
|
|
1029
1305
|
lang;
|
|
1030
1306
|
theme;
|
|
1031
1307
|
/**
|
|
@@ -1037,6 +1313,10 @@ var CodeBlock = class extends UIComponent {
|
|
|
1037
1313
|
pad;
|
|
1038
1314
|
codeFont;
|
|
1039
1315
|
selectable;
|
|
1316
|
+
/** Whether the language header band is drawn. See {@link CodeBlockOptions.showLanguage}. */
|
|
1317
|
+
showLanguage;
|
|
1318
|
+
/** Font of the header label, resolved once from the theme. */
|
|
1319
|
+
langFont;
|
|
1040
1320
|
/**
|
|
1041
1321
|
* @param theme Any subset of {@link MarkdownTheme}, or the name of a built-in
|
|
1042
1322
|
* preset (see {@link MarkdownThemePresetName}). Accepting a partial theme
|
|
@@ -1048,7 +1328,7 @@ var CodeBlock = class extends UIComponent {
|
|
|
1048
1328
|
* be constructed directly with a preset name without going through
|
|
1049
1329
|
* `Markdown`.
|
|
1050
1330
|
*/
|
|
1051
|
-
constructor(code, lang, maxWidth, theme, selectable = true) {
|
|
1331
|
+
constructor(code, lang, maxWidth, theme, selectable = true, options = {}) {
|
|
1052
1332
|
super();
|
|
1053
1333
|
const resolved = resolvePresetTheme(theme);
|
|
1054
1334
|
this.source = code;
|
|
@@ -1058,15 +1338,129 @@ var CodeBlock = class extends UIComponent {
|
|
|
1058
1338
|
this.pad = resolved.codePadding;
|
|
1059
1339
|
this.codeFont = `${resolved.codeFontSize}px ${resolved.codeFont}`;
|
|
1060
1340
|
this.selectable = selectable;
|
|
1341
|
+
this.langFont = `${resolved.codeLangFontSize}px ${resolved.codeFont}`;
|
|
1342
|
+
this.showLanguage = options.showLanguage === true && this.languageLabel() !== "";
|
|
1061
1343
|
this.lines = [];
|
|
1062
1344
|
this.width = maxWidth;
|
|
1063
1345
|
this.buildLines(code);
|
|
1346
|
+
this.on("wheel", (e) => {
|
|
1347
|
+
const max = this.maxScrollX;
|
|
1348
|
+
if (max <= 0) return;
|
|
1349
|
+
if (e.ctrlKey === true) return;
|
|
1350
|
+
const deltaMode = e.deltaMode ?? 0;
|
|
1351
|
+
let deltaX = e.deltaX ?? 0;
|
|
1352
|
+
let deltaY = e.deltaY ?? 0;
|
|
1353
|
+
if (deltaMode === 1) {
|
|
1354
|
+
deltaX *= 16;
|
|
1355
|
+
deltaY *= 16;
|
|
1356
|
+
} else if (deltaMode === 2) {
|
|
1357
|
+
deltaX *= this.width;
|
|
1358
|
+
deltaY *= this.height;
|
|
1359
|
+
}
|
|
1360
|
+
const horizontal = e.shiftKey === true ? deltaY || deltaX : deltaX;
|
|
1361
|
+
if (horizontal === 0) return;
|
|
1362
|
+
const before = this.scrollX;
|
|
1363
|
+
this.setScrollX(before + horizontal);
|
|
1364
|
+
if (this.scrollX !== before) e.nativeEvent?.preventDefault?.();
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* The language name shown in the header, or `''` when there is nothing to show.
|
|
1369
|
+
*
|
|
1370
|
+
* Normalized exactly as the highlighter normalizes its lookup key, so the label
|
|
1371
|
+
* and the colouring can never disagree about which language this is: a fence
|
|
1372
|
+
* may be written ` ```Bash ` or carry attributes (` ```ts title="a.ts" `), and
|
|
1373
|
+
* the label has to be the language, not the raw info string.
|
|
1374
|
+
*
|
|
1375
|
+
* Lowercased for the same reason `streamdown` lowercases its own
|
|
1376
|
+
* (`lib/code-block/header.tsx:15`): the fence's capitalization is incidental,
|
|
1377
|
+
* and a document mixing ` ```JS ` with ` ```js ` should not render two
|
|
1378
|
+
* different-looking labels for one language.
|
|
1379
|
+
*/
|
|
1380
|
+
languageLabel() {
|
|
1381
|
+
return this.lang.trim().toLowerCase().split(/[\s:,{]/)[0] ?? "";
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Height in px of the header band, or `0` when it is off.
|
|
1385
|
+
*
|
|
1386
|
+
* The label sits in a band of its own rather than floating over the code,
|
|
1387
|
+
* because a translucent overlay above real glyphs is unreadable at small sizes
|
|
1388
|
+
* and would fight the horizontal scroll: the code slides under it, so any text
|
|
1389
|
+
* drawn on top would collide with a different token every frame.
|
|
1390
|
+
*/
|
|
1391
|
+
headerHeight() {
|
|
1392
|
+
if (!this.showLanguage) return 0;
|
|
1393
|
+
return this.theme.codeLangFontSize + Math.round(this.pad * 0.75);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Local y of the first line of code.
|
|
1397
|
+
*
|
|
1398
|
+
* Everything that positions a row — the painter, the projection, the grid's
|
|
1399
|
+
* own origin — goes through this, so the header offset cannot be applied to
|
|
1400
|
+
* one and forgotten on another. That class of mismatch is exactly what
|
|
1401
|
+
* detaches selection carriers from the glyphs they cover.
|
|
1402
|
+
*/
|
|
1403
|
+
contentTop() {
|
|
1404
|
+
return this.headerHeight() + this.pad;
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Current horizontal scroll offset in local px, clamped to what the content
|
|
1408
|
+
* currently allows.
|
|
1409
|
+
*
|
|
1410
|
+
* Clamped on READ, not only on write, because `setWidth()` may shrink the box
|
|
1411
|
+
* after a scroll and is contractually forbidden from rebuilding anything. Both
|
|
1412
|
+
* the painter and the projection read through here, which is what keeps the
|
|
1413
|
+
* glyphs and the selection carriers on the same offset within a frame.
|
|
1414
|
+
*/
|
|
1415
|
+
get scrollX() {
|
|
1416
|
+
return Math.min(this.scrollXValue, this.maxScrollX);
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Widest line's overflow past the padded box, i.e. the maximum useful
|
|
1420
|
+
* {@link scrollX}. `0` when every line already fits.
|
|
1421
|
+
*/
|
|
1422
|
+
get maxScrollX() {
|
|
1423
|
+
return Math.max(0, this.contentWidth() - (this.width - this.pad * 2));
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Widest prepared line, memoized against the grid that produced it.
|
|
1427
|
+
*
|
|
1428
|
+
* Read by {@link scrollX}, which both `render()` and `getContentProjection()`
|
|
1429
|
+
* call every synced frame, so an O(lines) scan here would be an O(document) cost
|
|
1430
|
+
* per frame on a long block — the exact shape the per-line projection window
|
|
1431
|
+
* exists to avoid. The grid is rebuilt only when the content changes, so the
|
|
1432
|
+
* cache key is identity of the grid object.
|
|
1433
|
+
*/
|
|
1434
|
+
contentWidth() {
|
|
1435
|
+
const grid = this.ensureGrid();
|
|
1436
|
+
if (this.contentWidthGrid === grid) return this.contentWidthValue;
|
|
1437
|
+
let widest = 0;
|
|
1438
|
+
for (const line of grid.lines) {
|
|
1439
|
+
if (line.width > widest) widest = line.width;
|
|
1440
|
+
}
|
|
1441
|
+
this.contentWidthGrid = grid;
|
|
1442
|
+
this.contentWidthValue = widest;
|
|
1443
|
+
return widest;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Scroll horizontally to `x`, clamped to `[0, maxScrollX]`.
|
|
1447
|
+
*
|
|
1448
|
+
* @returns `this` for chaining.
|
|
1449
|
+
*/
|
|
1450
|
+
setScrollX(x) {
|
|
1451
|
+
const next = Math.max(0, Math.min(this.maxScrollX, x));
|
|
1452
|
+
if (next === this.scrollXValue) return this;
|
|
1453
|
+
this.scrollXValue = next;
|
|
1454
|
+
this.contentEpoch++;
|
|
1455
|
+
this.scene?.markDirty();
|
|
1456
|
+
return this;
|
|
1064
1457
|
}
|
|
1065
1458
|
/** Re-parse code content (e.g. for live editing). */
|
|
1066
1459
|
setCode(code, lang) {
|
|
1067
1460
|
if (lang !== void 0) this.lang = lang;
|
|
1068
1461
|
this.source = code;
|
|
1069
1462
|
this.buildLines(code);
|
|
1463
|
+
this.scrollXValue = Math.min(this.scrollXValue, this.maxScrollX);
|
|
1070
1464
|
this.scene?.markDirty();
|
|
1071
1465
|
return this;
|
|
1072
1466
|
}
|
|
@@ -1086,9 +1480,13 @@ var CodeBlock = class extends UIComponent {
|
|
|
1086
1480
|
* Deliberately does **not** rebuild the grid or the highlight, because code does
|
|
1087
1481
|
* not reflow: lines are placed on a fixed monospace grid at `col × cellWidth` and
|
|
1088
1482
|
* a long line overflows rather than wrapping, so `height` is a function of line
|
|
1089
|
-
* *count* alone. The width
|
|
1090
|
-
* change the glyph geometry — the source, the language, the font —
|
|
1091
|
-
* {@link setCode} and invalidates the grid there.
|
|
1483
|
+
* *count* alone. The width sizes the rounded background and the clip. Anything
|
|
1484
|
+
* that would change the glyph geometry — the source, the language, the font —
|
|
1485
|
+
* goes through {@link setCode} and invalidates the grid there.
|
|
1486
|
+
*
|
|
1487
|
+
* A narrower box can leave {@link scrollX} past the new end of travel. That is
|
|
1488
|
+
* resolved by clamping on read rather than by adjusting anything here, so this
|
|
1489
|
+
* method keeps costing nothing.
|
|
1092
1490
|
*
|
|
1093
1491
|
* @returns `this` for chaining.
|
|
1094
1492
|
*/
|
|
@@ -1102,16 +1500,21 @@ var CodeBlock = class extends UIComponent {
|
|
|
1102
1500
|
getContentProjection(hint) {
|
|
1103
1501
|
if (!this.source) return null;
|
|
1104
1502
|
const grid = this.ensureGrid();
|
|
1503
|
+
const scrollX = this.scrollX;
|
|
1105
1504
|
const rows = [];
|
|
1106
1505
|
rows.length = grid.lines.length;
|
|
1107
1506
|
for (let row = 0; row < grid.lines.length; row++) {
|
|
1108
1507
|
const line = grid.lines[row];
|
|
1109
|
-
const y = this.
|
|
1508
|
+
const y = this.contentTop() + row * this.lineH;
|
|
1110
1509
|
if (!contentLineInHint(hint, y, this.lineH)) continue;
|
|
1111
1510
|
rows[row] = {
|
|
1112
1511
|
text: this.source.slice(line.sourceStart, line.sourceEnd),
|
|
1113
1512
|
separatorAfter: this.source.slice(line.sourceEnd, line.nextSourceStart) || void 0,
|
|
1114
|
-
|
|
1513
|
+
// The SAME offset `render()` subtracts, read through the same clamping
|
|
1514
|
+
// accessor. Cell carriers are `position: relative` inside this `absolute`
|
|
1515
|
+
// line box, so shifting the line's x translates every cell of the line
|
|
1516
|
+
// rigidly and selection stays over the glyphs.
|
|
1517
|
+
x: this.pad - scrollX,
|
|
1115
1518
|
y,
|
|
1116
1519
|
baseline: this.lineH * 0.75,
|
|
1117
1520
|
font: this.codeFont,
|
|
@@ -1131,6 +1534,12 @@ var CodeBlock = class extends UIComponent {
|
|
|
1131
1534
|
// render() draws cell-by-cell (no ligatures can form); the DOM copy
|
|
1132
1535
|
// must not ligate either or Firefox selection geometry drifts.
|
|
1133
1536
|
ligatures: "none",
|
|
1537
|
+
// `render()` clips the glyph pass to this box, so the DOM copy must too.
|
|
1538
|
+
// A line wider than the box otherwise projects carriers past the entity,
|
|
1539
|
+
// and the browser paints their selection highlight over whatever is drawn
|
|
1540
|
+
// beside the block — measured 1580px of carrier against a 1566px viewport
|
|
1541
|
+
// on a real page, the highlight running through the prose to its right.
|
|
1542
|
+
clipToBounds: true,
|
|
1134
1543
|
grid
|
|
1135
1544
|
};
|
|
1136
1545
|
}
|
|
@@ -1145,6 +1554,11 @@ var CodeBlock = class extends UIComponent {
|
|
|
1145
1554
|
*
|
|
1146
1555
|
* The last previously-seen line is deliberately NOT reused: a chunk usually
|
|
1147
1556
|
* lands mid-line, so that line's text (and therefore its tokenization) changes.
|
|
1557
|
+
*
|
|
1558
|
+
* Prefix reuse survives multi-line constructs because {@link lineCarry} records
|
|
1559
|
+
* the state ENTERING each line, so resuming at the reuse boundary needs no
|
|
1560
|
+
* rescan: a carried state is a pure function of the preceding text, and that
|
|
1561
|
+
* text is byte-identical over the reused prefix by construction.
|
|
1148
1562
|
*/
|
|
1149
1563
|
buildLines(code) {
|
|
1150
1564
|
this.contentEpoch++;
|
|
@@ -1155,18 +1569,20 @@ var CodeBlock = class extends UIComponent {
|
|
|
1155
1569
|
const limit = Math.min(previous.length - 1, rawLines.length);
|
|
1156
1570
|
while (reusable < limit && previous[reusable] === rawLines[reusable]) reusable++;
|
|
1157
1571
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
this.
|
|
1164
|
-
|
|
1165
|
-
|
|
1572
|
+
const lines = reusable > 0 ? this.lines.slice(0, reusable) : [];
|
|
1573
|
+
const carries = reusable > 0 ? this.lineCarry.slice(0, reusable) : [];
|
|
1574
|
+
let carry = reusable > 0 ? this.lineCarry[reusable] ?? null : null;
|
|
1575
|
+
for (let i = reusable; i < rawLines.length; i++) {
|
|
1576
|
+
carries.push(carry);
|
|
1577
|
+
const result = highlightLine(rawLines[i], this.lang, this.theme, carry);
|
|
1578
|
+
lines.push(result.segments);
|
|
1579
|
+
carry = result.carry;
|
|
1166
1580
|
}
|
|
1581
|
+
this.lines = lines;
|
|
1582
|
+
this.lineCarry = carries;
|
|
1167
1583
|
this.rawLines = rawLines;
|
|
1168
1584
|
this.grid = null;
|
|
1169
|
-
this.height = this.
|
|
1585
|
+
this.height = this.contentTop() + this.pad + rawLines.length * this.lineH;
|
|
1170
1586
|
}
|
|
1171
1587
|
ensureGrid() {
|
|
1172
1588
|
const cellWidth = this.cellWidth || Math.max(1, measureText("M", this.codeFont));
|
|
@@ -1180,7 +1596,15 @@ var CodeBlock = class extends UIComponent {
|
|
|
1180
1596
|
}
|
|
1181
1597
|
return this.grid;
|
|
1182
1598
|
}
|
|
1183
|
-
/**
|
|
1599
|
+
/**
|
|
1600
|
+
* Not hit-testable, and deliberately still not `interactive`, even though the
|
|
1601
|
+
* block now consumes wheel events to scroll.
|
|
1602
|
+
*
|
|
1603
|
+
* The wheel arrives from the content-projection div rather than from canvas
|
|
1604
|
+
* hit-testing, so no a11y shadow node is needed. Creating one would place a
|
|
1605
|
+
* `pointer-events: auto` element above the transparent text mirror and swallow
|
|
1606
|
+
* the mousedown that starts a native drag-selection.
|
|
1607
|
+
*/
|
|
1184
1608
|
isPointInside() {
|
|
1185
1609
|
return false;
|
|
1186
1610
|
}
|
|
@@ -1192,8 +1616,25 @@ var CodeBlock = class extends UIComponent {
|
|
|
1192
1616
|
const atlas = codeGlyphAtlas(r);
|
|
1193
1617
|
const atlasSource = atlas?.source ?? null;
|
|
1194
1618
|
const blit = atlas ? r.drawImageRect : void 0;
|
|
1619
|
+
const header = this.headerHeight();
|
|
1620
|
+
if (header > 0) {
|
|
1621
|
+
r.fillText(
|
|
1622
|
+
this.languageLabel(),
|
|
1623
|
+
this.pad,
|
|
1624
|
+
// Vertically centred in the band by its own cap height rather than by
|
|
1625
|
+
// font size: `fillText` takes a baseline, so centring the em box would
|
|
1626
|
+
// sit the visible letterforms low. 0.7 of the label size below the band's
|
|
1627
|
+
// centre line is where a lowercase-plus-cap run reads as centred.
|
|
1628
|
+
(header + this.theme.codeLangFontSize * 0.7) / 2,
|
|
1629
|
+
this.langFont,
|
|
1630
|
+
this.theme.codeLangColor
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
r.save();
|
|
1634
|
+
r.clip(0, header, this.width, this.height - header);
|
|
1635
|
+
const scrollX = this.scrollX;
|
|
1195
1636
|
for (let row = 0; row < grid.lines.length; row++) {
|
|
1196
|
-
const yBaseline = this.
|
|
1637
|
+
const yBaseline = this.contentTop() + row * this.lineH + this.lineH * 0.75;
|
|
1197
1638
|
const segments = this.lines[row];
|
|
1198
1639
|
let segmentIndex = 0;
|
|
1199
1640
|
let segmentEnd = segments[0]?.text.length ?? 0;
|
|
@@ -1207,7 +1648,8 @@ var CodeBlock = class extends UIComponent {
|
|
|
1207
1648
|
const sourceText = this.source.slice(cell.sourceStart, cell.sourceEnd);
|
|
1208
1649
|
if (cell.advance <= 0 || sourceText === " " || sourceText === " ") continue;
|
|
1209
1650
|
const color = segments[segmentIndex]?.color ?? this.theme.codeColor;
|
|
1210
|
-
const x = this.pad + cell.x;
|
|
1651
|
+
const x = this.pad + cell.x - scrollX;
|
|
1652
|
+
if (x + cell.advance < 0 || x > this.width) continue;
|
|
1211
1653
|
if (blit && atlas) {
|
|
1212
1654
|
const slot = atlas.get(this.codeFont, color, cell.glyph);
|
|
1213
1655
|
const src = atlasSource ?? atlas.source;
|
|
@@ -1230,6 +1672,7 @@ var CodeBlock = class extends UIComponent {
|
|
|
1230
1672
|
r.fillText(cell.glyph, x, yBaseline, this.codeFont, color);
|
|
1231
1673
|
}
|
|
1232
1674
|
}
|
|
1675
|
+
r.restore();
|
|
1233
1676
|
}
|
|
1234
1677
|
};
|
|
1235
1678
|
var codeAtlases = /* @__PURE__ */ new Map();
|
|
@@ -2481,6 +2924,20 @@ function defaultSaveFile(filename, content, mimeType) {
|
|
|
2481
2924
|
doc.body.removeChild(anchor);
|
|
2482
2925
|
URL.revokeObjectURL(url);
|
|
2483
2926
|
}
|
|
2927
|
+
function resolveBlockAffordanceConfig(config = {}) {
|
|
2928
|
+
return {
|
|
2929
|
+
copy: config.copy ?? true,
|
|
2930
|
+
download: config.download ?? true,
|
|
2931
|
+
labels: {
|
|
2932
|
+
copyCode: config.labels?.copyCode ?? "Copy code",
|
|
2933
|
+
downloadCode: config.labels?.downloadCode ?? "Download code",
|
|
2934
|
+
copyTable: config.labels?.copyTable ?? "Copy table",
|
|
2935
|
+
downloadTable: config.labels?.downloadTable ?? "Download table",
|
|
2936
|
+
copied: config.labels?.copied ?? "Copied",
|
|
2937
|
+
saved: config.labels?.saved ?? "Saved"
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2484
2941
|
var BlockAffordanceButton = class _BlockAffordanceButton extends Button {
|
|
2485
2942
|
constructor(label, successLabel, act, opts = {}) {
|
|
2486
2943
|
super(label, { ...opts, onClick: () => this.run() });
|
|
@@ -2802,6 +3259,22 @@ var Markdown = class _Markdown extends UIComponent3 {
|
|
|
2802
3259
|
* affordance.
|
|
2803
3260
|
*/
|
|
2804
3261
|
blockAffordances;
|
|
3262
|
+
/**
|
|
3263
|
+
* Which controls a block carries and what they are called, with defaults
|
|
3264
|
+
* applied.
|
|
3265
|
+
*
|
|
3266
|
+
* Resolved once in the constructor rather than per block: the defaults are
|
|
3267
|
+
* fixed, and re-deriving them for every code fence in a long document would
|
|
3268
|
+
* repeat the same six `??` fallbacks for no benefit.
|
|
3269
|
+
*/
|
|
3270
|
+
affordanceConfig;
|
|
3271
|
+
/**
|
|
3272
|
+
* Whether code blocks show their language in a header band.
|
|
3273
|
+
*
|
|
3274
|
+
* Read when a block entity is built, exactly like {@link blockAffordances}, so
|
|
3275
|
+
* it affects blocks rendered from here on rather than retroactively.
|
|
3276
|
+
*/
|
|
3277
|
+
showCodeLanguage;
|
|
2805
3278
|
/** Clipboard writer used by the copy controls. */
|
|
2806
3279
|
writeClipboard;
|
|
2807
3280
|
/** File saver used by the download controls. */
|
|
@@ -3084,6 +3557,8 @@ var Markdown = class _Markdown extends UIComponent3 {
|
|
|
3084
3557
|
this.selectable = opts.selectable ?? true;
|
|
3085
3558
|
this._userTiming = opts.userTiming ?? false;
|
|
3086
3559
|
this.blockAffordances = opts.blockAffordances ?? false;
|
|
3560
|
+
this.affordanceConfig = resolveBlockAffordanceConfig(opts.affordances);
|
|
3561
|
+
this.showCodeLanguage = opts.showCodeLanguage ?? false;
|
|
3087
3562
|
this.writeClipboard = opts.writeClipboard ?? defaultWriteClipboard;
|
|
3088
3563
|
this.saveFile = opts.saveFile ?? defaultSaveFile;
|
|
3089
3564
|
this.content = new Stack({
|
|
@@ -4045,40 +4520,60 @@ var Markdown = class _Markdown extends UIComponent3 {
|
|
|
4045
4520
|
const controls = make();
|
|
4046
4521
|
return controls.length > 0 ? new BlockWithAffordances(block, controls) : block;
|
|
4047
4522
|
}
|
|
4048
|
-
/** Copy and download controls for one fenced code block. */
|
|
4523
|
+
/** Copy and download controls for one fenced code block, per {@link affordanceConfig}. */
|
|
4049
4524
|
codeBlockAffordances(source, lang) {
|
|
4050
4525
|
const opts = this.affordanceButtonOptions();
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4526
|
+
const { copy, download, labels } = this.affordanceConfig;
|
|
4527
|
+
const controls = [];
|
|
4528
|
+
if (copy) {
|
|
4529
|
+
controls.push(
|
|
4530
|
+
new BlockAffordanceButton(
|
|
4531
|
+
labels.copyCode,
|
|
4532
|
+
labels.copied,
|
|
4533
|
+
() => this.writeClipboard(source),
|
|
4534
|
+
opts
|
|
4535
|
+
)
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
if (download) {
|
|
4539
|
+
controls.push(
|
|
4540
|
+
new BlockAffordanceButton(
|
|
4541
|
+
labels.downloadCode,
|
|
4542
|
+
labels.saved,
|
|
4543
|
+
() => this.saveFile(`code.${extensionForLanguage(lang)}`, source, mimeForLanguage(lang)),
|
|
4544
|
+
opts
|
|
4545
|
+
)
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
return controls;
|
|
4549
|
+
}
|
|
4550
|
+
/** Copy (as Markdown) and download (as CSV) controls for one table, per {@link affordanceConfig}. */
|
|
4062
4551
|
tableAffordances(tblToken) {
|
|
4552
|
+
const { copy, download, labels } = this.affordanceConfig;
|
|
4063
4553
|
const content = tableContentOf(tblToken);
|
|
4064
4554
|
const opts = this.affordanceButtonOptions();
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
)
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4555
|
+
const controls = [];
|
|
4556
|
+
if (copy) {
|
|
4557
|
+
controls.push(
|
|
4558
|
+
new BlockAffordanceButton(
|
|
4559
|
+
labels.copyTable,
|
|
4560
|
+
labels.copied,
|
|
4561
|
+
() => this.writeClipboard(tableToMarkdown(content)),
|
|
4562
|
+
opts
|
|
4563
|
+
)
|
|
4564
|
+
);
|
|
4565
|
+
}
|
|
4566
|
+
if (download) {
|
|
4567
|
+
controls.push(
|
|
4568
|
+
new BlockAffordanceButton(
|
|
4569
|
+
labels.downloadTable,
|
|
4570
|
+
labels.saved,
|
|
4571
|
+
() => this.saveFile("table.csv", tableToCsv(content), "text/csv;charset=utf-8"),
|
|
4572
|
+
opts
|
|
4573
|
+
)
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
4576
|
+
return controls;
|
|
4082
4577
|
}
|
|
4083
4578
|
/**
|
|
4084
4579
|
* Button styling for the affordances, derived from the document theme.
|
|
@@ -5222,7 +5717,9 @@ var Markdown = class _Markdown extends UIComponent3 {
|
|
|
5222
5717
|
}
|
|
5223
5718
|
}
|
|
5224
5719
|
return this.withBlockAffordances(
|
|
5225
|
-
new CodeBlock(codeToken.text, lang, availableWidth, t, this.selectable
|
|
5720
|
+
new CodeBlock(codeToken.text, lang, availableWidth, t, this.selectable, {
|
|
5721
|
+
showLanguage: this.showCodeLanguage
|
|
5722
|
+
}),
|
|
5226
5723
|
() => this.codeBlockAffordances(codeToken.text, lang)
|
|
5227
5724
|
);
|
|
5228
5725
|
}
|
|
@@ -5453,6 +5950,7 @@ export {
|
|
|
5453
5950
|
extensionForLanguage,
|
|
5454
5951
|
footnoteMarker,
|
|
5455
5952
|
hasFencedBlockRenderer,
|
|
5953
|
+
highlightedLanguages,
|
|
5456
5954
|
isFencedBlockRendererReady,
|
|
5457
5955
|
isMathJaxReady,
|
|
5458
5956
|
isPresetName,
|
|
@@ -5461,6 +5959,7 @@ export {
|
|
|
5461
5959
|
preloadMathJax,
|
|
5462
5960
|
registerFencedBlockRenderer,
|
|
5463
5961
|
renderFencedBlock,
|
|
5962
|
+
resolveBlockAffordanceConfig,
|
|
5464
5963
|
resolvePresetTheme,
|
|
5465
5964
|
scanFrontMatter,
|
|
5466
5965
|
tableContentOf,
|