slackmark 0.3.0 → 0.5.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/README.md +22 -17
- package/dist/adapters/html-block-adapter.d.ts.map +1 -1
- package/dist/adapters/inline.d.ts +6 -0
- package/dist/adapters/inline.d.ts.map +1 -1
- package/dist/adapters/math-block-adapter.d.ts +1 -1
- package/dist/adapters/mermaid-fence-adapter.d.ts.map +1 -1
- package/dist/adapters/paragraph-adapter.d.ts.map +1 -1
- package/dist/adapters/table-adapter.d.ts.map +1 -1
- package/dist/assemble/message-assembler.d.ts.map +1 -1
- package/dist/blocks/types.d.ts +33 -11
- package/dist/blocks/types.d.ts.map +1 -1
- package/dist/capabilities/capabilities.d.ts +2 -5
- package/dist/capabilities/capabilities.d.ts.map +1 -1
- package/dist/converter/slackmark-converter.d.ts.map +1 -1
- package/dist/index.js +738 -395
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utf16.d.ts +20 -0
- package/dist/utf16.d.ts.map +1 -0
- package/dist/validate/validate-blocks.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ConfigurationError = class extends Error {
|
|
3
|
+
name;
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
6
|
+
this.name = "ConfigurationError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
1
10
|
// src/capabilities/capabilities.ts
|
|
2
11
|
var CapabilityPresetName = {
|
|
3
12
|
Latest: "latest",
|
|
@@ -9,7 +18,6 @@ var CapabilityPresetName = {
|
|
|
9
18
|
D2026_06: "2026-06"
|
|
10
19
|
};
|
|
11
20
|
var ALL_OFF = {
|
|
12
|
-
richText: false,
|
|
13
21
|
markdownBlock: false,
|
|
14
22
|
tableBlock: false,
|
|
15
23
|
preformattedLanguage: false,
|
|
@@ -18,10 +26,7 @@ var ALL_OFF = {
|
|
|
18
26
|
dataVisualization: false,
|
|
19
27
|
containerBlock: false
|
|
20
28
|
};
|
|
21
|
-
var CONSERVATIVE = {
|
|
22
|
-
...ALL_OFF,
|
|
23
|
-
richText: true
|
|
24
|
-
};
|
|
29
|
+
var CONSERVATIVE = { ...ALL_OFF };
|
|
25
30
|
var P_2025_02 = {
|
|
26
31
|
...CONSERVATIVE,
|
|
27
32
|
markdownBlock: true
|
|
@@ -59,12 +64,13 @@ function resolveCapabilities(input, overrides) {
|
|
|
59
64
|
if (input === void 0) {
|
|
60
65
|
base = CAPABILITY_PRESETS.latest;
|
|
61
66
|
} else if (typeof input === "string") {
|
|
62
|
-
const preset = CAPABILITY_PRESETS[input];
|
|
67
|
+
const preset = isPresetName(input) ? CAPABILITY_PRESETS[input] : void 0;
|
|
63
68
|
if (preset === void 0) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
throw new ConfigurationError(
|
|
70
|
+
`Unknown capability preset ${JSON.stringify(input)}; use one of: ${Object.keys(CAPABILITY_PRESETS).join(", ")}.`
|
|
71
|
+
);
|
|
67
72
|
}
|
|
73
|
+
base = preset;
|
|
68
74
|
} else if (isFullFlags(input)) {
|
|
69
75
|
base = input;
|
|
70
76
|
} else {
|
|
@@ -75,8 +81,11 @@ function resolveCapabilities(input, overrides) {
|
|
|
75
81
|
}
|
|
76
82
|
return Object.freeze({ ...base, ...overrides });
|
|
77
83
|
}
|
|
84
|
+
function isPresetName(value) {
|
|
85
|
+
return Object.hasOwn(CAPABILITY_PRESETS, value);
|
|
86
|
+
}
|
|
78
87
|
function isFullFlags(value) {
|
|
79
|
-
return typeof value.
|
|
88
|
+
return typeof value.markdownBlock === "boolean" && typeof value.tableBlock === "boolean" && typeof value.preformattedLanguage === "boolean" && typeof value.headerLevel === "boolean" && typeof value.dataTable === "boolean" && typeof value.dataVisualization === "boolean" && typeof value.containerBlock === "boolean";
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
// src/constants.ts
|
|
@@ -298,6 +307,29 @@ function assertNever(value, message) {
|
|
|
298
307
|
throw new Error(`${detail}: ${JSON.stringify(value)}`);
|
|
299
308
|
}
|
|
300
309
|
|
|
310
|
+
// src/utf16.ts
|
|
311
|
+
var HIGH_SURROGATE_MIN = 55296;
|
|
312
|
+
var HIGH_SURROGATE_MAX = 56319;
|
|
313
|
+
var LOW_SURROGATE_MIN = 56320;
|
|
314
|
+
var LOW_SURROGATE_MAX = 57343;
|
|
315
|
+
function surrogateSafeEnd(text, end) {
|
|
316
|
+
if (end <= 0) {
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
if (end >= text.length) {
|
|
320
|
+
return text.length;
|
|
321
|
+
}
|
|
322
|
+
const before = text.charCodeAt(end - 1);
|
|
323
|
+
const after = text.charCodeAt(end);
|
|
324
|
+
if (before >= HIGH_SURROGATE_MIN && before <= HIGH_SURROGATE_MAX && after >= LOW_SURROGATE_MIN && after <= LOW_SURROGATE_MAX) {
|
|
325
|
+
return end - 1;
|
|
326
|
+
}
|
|
327
|
+
return end;
|
|
328
|
+
}
|
|
329
|
+
function sliceSurrogateSafe(text, max) {
|
|
330
|
+
return text.slice(0, surrogateSafeEnd(text, max));
|
|
331
|
+
}
|
|
332
|
+
|
|
301
333
|
// src/types.ts
|
|
302
334
|
var OverflowMode = {
|
|
303
335
|
Degrade: "degrade",
|
|
@@ -398,7 +430,7 @@ var MessageAssembler = class {
|
|
|
398
430
|
reason: "Notification text truncated to 40k safety limit",
|
|
399
431
|
path: ctx.path
|
|
400
432
|
});
|
|
401
|
-
return { blocks, text: raw
|
|
433
|
+
return { blocks, text: sliceSurrogateSafe(raw, MESSAGE_TEXT_SAFETY_MAX_CHARS) };
|
|
402
434
|
}
|
|
403
435
|
};
|
|
404
436
|
function buildFootnoteBlocks(entries, degradations) {
|
|
@@ -417,7 +449,7 @@ function buildFootnoteBlocks(entries, degradations) {
|
|
|
417
449
|
reason: "Footnote context text truncated to 3000 chars",
|
|
418
450
|
path: "footnote"
|
|
419
451
|
});
|
|
420
|
-
elements.push({ type: "mrkdwn", text: note
|
|
452
|
+
elements.push({ type: "mrkdwn", text: sliceSurrogateSafe(note, SECTION_TEXT_MAX_CHARS) });
|
|
421
453
|
} else {
|
|
422
454
|
elements.push({ type: "mrkdwn", text: note });
|
|
423
455
|
}
|
|
@@ -622,7 +654,7 @@ function inlinesToPlain(elements) {
|
|
|
622
654
|
parts.push(`@${el.range}`);
|
|
623
655
|
break;
|
|
624
656
|
case "date":
|
|
625
|
-
parts.push(el.fallback ?? el.format);
|
|
657
|
+
parts.push(el.fallback ?? formatDateTokensUtc(el.timestamp, el.format));
|
|
626
658
|
break;
|
|
627
659
|
default:
|
|
628
660
|
break;
|
|
@@ -630,6 +662,101 @@ function inlinesToPlain(elements) {
|
|
|
630
662
|
}
|
|
631
663
|
return parts.join("");
|
|
632
664
|
}
|
|
665
|
+
var DATE_TOKEN_RE = /\{([a-z_]+)\}/g;
|
|
666
|
+
var MONTHS = [
|
|
667
|
+
"January",
|
|
668
|
+
"February",
|
|
669
|
+
"March",
|
|
670
|
+
"April",
|
|
671
|
+
"May",
|
|
672
|
+
"June",
|
|
673
|
+
"July",
|
|
674
|
+
"August",
|
|
675
|
+
"September",
|
|
676
|
+
"October",
|
|
677
|
+
"November",
|
|
678
|
+
"December"
|
|
679
|
+
];
|
|
680
|
+
var WEEKDAYS = [
|
|
681
|
+
"Sunday",
|
|
682
|
+
"Monday",
|
|
683
|
+
"Tuesday",
|
|
684
|
+
"Wednesday",
|
|
685
|
+
"Thursday",
|
|
686
|
+
"Friday",
|
|
687
|
+
"Saturday"
|
|
688
|
+
];
|
|
689
|
+
function pad2(value) {
|
|
690
|
+
return String(value).padStart(2, "0");
|
|
691
|
+
}
|
|
692
|
+
function ordinal(value) {
|
|
693
|
+
const remainder = value % 100;
|
|
694
|
+
if (remainder >= 11 && remainder <= 13) {
|
|
695
|
+
return `${String(value)}th`;
|
|
696
|
+
}
|
|
697
|
+
switch (value % 10) {
|
|
698
|
+
case 1:
|
|
699
|
+
return `${String(value)}st`;
|
|
700
|
+
case 2:
|
|
701
|
+
return `${String(value)}nd`;
|
|
702
|
+
case 3:
|
|
703
|
+
return `${String(value)}rd`;
|
|
704
|
+
default:
|
|
705
|
+
return `${String(value)}th`;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function dateToken(token, date) {
|
|
709
|
+
const day = date.getUTCDate();
|
|
710
|
+
const month = MONTHS[date.getUTCMonth()] ?? "";
|
|
711
|
+
const shortMonth = month.slice(0, 3);
|
|
712
|
+
const weekday = WEEKDAYS[date.getUTCDay()] ?? "";
|
|
713
|
+
const year = date.getUTCFullYear();
|
|
714
|
+
switch (token) {
|
|
715
|
+
case "date_num":
|
|
716
|
+
return `${String(year)}-${pad2(date.getUTCMonth() + 1)}-${pad2(day)}`;
|
|
717
|
+
// Slack renders {date_slash} month-first: 02/18/2014 (docs example for
|
|
718
|
+
// timestamp 1392734382).
|
|
719
|
+
case "date_slash":
|
|
720
|
+
return `${pad2(date.getUTCMonth() + 1)}/${pad2(day)}/${String(year)}`;
|
|
721
|
+
case "date_long":
|
|
722
|
+
case "date_long_pretty":
|
|
723
|
+
return `${weekday}, ${month} ${ordinal(day)}, ${String(year)}`;
|
|
724
|
+
case "date_long_full":
|
|
725
|
+
return `${month} ${String(day)}, ${String(year)}`;
|
|
726
|
+
// {date} renders "February 18th, 2014" (ordinal + year), not "February 18".
|
|
727
|
+
case "date":
|
|
728
|
+
case "date_pretty":
|
|
729
|
+
return `${month} ${ordinal(day)}, ${String(year)}`;
|
|
730
|
+
case "date_short":
|
|
731
|
+
case "date_short_pretty":
|
|
732
|
+
return `${shortMonth} ${String(day)}, ${String(year)}`;
|
|
733
|
+
case "time":
|
|
734
|
+
return `${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}`;
|
|
735
|
+
case "time_secs":
|
|
736
|
+
return `${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`;
|
|
737
|
+
default:
|
|
738
|
+
return void 0;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function formatDateTokensUtc(timestamp, format) {
|
|
742
|
+
const date = new Date(timestamp * 1e3);
|
|
743
|
+
if (!Number.isFinite(timestamp) || Number.isNaN(date.getTime())) {
|
|
744
|
+
return String(timestamp);
|
|
745
|
+
}
|
|
746
|
+
let unsupported = false;
|
|
747
|
+
const formatted = format.replace(DATE_TOKEN_RE, (_match, token) => {
|
|
748
|
+
const replacement = dateToken(token, date);
|
|
749
|
+
if (replacement === void 0) {
|
|
750
|
+
unsupported = true;
|
|
751
|
+
return "";
|
|
752
|
+
}
|
|
753
|
+
return replacement;
|
|
754
|
+
});
|
|
755
|
+
if (format.length === 0 || unsupported) {
|
|
756
|
+
return date.toISOString().slice(0, 10);
|
|
757
|
+
}
|
|
758
|
+
return formatted;
|
|
759
|
+
}
|
|
633
760
|
function tableToPlain(rows) {
|
|
634
761
|
const lines = [];
|
|
635
762
|
for (const row of rows) {
|
|
@@ -869,15 +996,6 @@ function parseNumberList(inner) {
|
|
|
869
996
|
// src/converter/slackmark-converter.ts
|
|
870
997
|
import { toString } from "mdast-util-to-string";
|
|
871
998
|
|
|
872
|
-
// src/errors.ts
|
|
873
|
-
var ConfigurationError = class extends Error {
|
|
874
|
-
name;
|
|
875
|
-
constructor(message, options) {
|
|
876
|
-
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
877
|
-
this.name = "ConfigurationError";
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
|
|
881
999
|
// src/converter/resolve-config.ts
|
|
882
1000
|
function resolveConfig(defaults, overrides) {
|
|
883
1001
|
const merged = {
|
|
@@ -967,7 +1085,7 @@ var SlackMarkConverter = class {
|
|
|
967
1085
|
enableMath: config.enableMath,
|
|
968
1086
|
enableFrontmatter: config.enableFrontmatter
|
|
969
1087
|
});
|
|
970
|
-
const { definitions, footnotes, footnoteOrder } = collectRefs(tree);
|
|
1088
|
+
const { definitions, footnotes, footnoteOrder } = collectRefs(tree, degradations);
|
|
971
1089
|
let precedingHeading;
|
|
972
1090
|
const emitted = [];
|
|
973
1091
|
let index = 0;
|
|
@@ -992,11 +1110,13 @@ var SlackMarkConverter = class {
|
|
|
992
1110
|
emitted.push(...blocks);
|
|
993
1111
|
index += 1;
|
|
994
1112
|
}
|
|
1113
|
+
const footnoteNumbers = /* @__PURE__ */ new Map();
|
|
1114
|
+
footnoteOrder.forEach((id, i) => footnoteNumbers.set(id, i + 1));
|
|
995
1115
|
const footnoteEntries = footnoteOrder.map((id, i) => {
|
|
996
1116
|
const def = footnotes.get(id);
|
|
997
1117
|
return {
|
|
998
1118
|
n: i + 1,
|
|
999
|
-
text: def !== void 0 ?
|
|
1119
|
+
text: def !== void 0 ? footnoteBodyText(def, footnoteNumbers).trim() : id
|
|
1000
1120
|
};
|
|
1001
1121
|
});
|
|
1002
1122
|
const footnoteBlocks = buildFootnoteBlocks(footnoteEntries, degradations);
|
|
@@ -1105,7 +1225,7 @@ function safeNotificationText(plainText, markdown, degradations) {
|
|
|
1105
1225
|
reason: "Notification text truncated to 40k safety limit",
|
|
1106
1226
|
path: "root"
|
|
1107
1227
|
});
|
|
1108
|
-
return raw
|
|
1228
|
+
return sliceSurrogateSafe(raw, MESSAGE_TEXT_SAFETY_MAX_CHARS);
|
|
1109
1229
|
}
|
|
1110
1230
|
function clampSectionFallback(text, degradations) {
|
|
1111
1231
|
if (text.length <= SECTION_TEXT_MAX_CHARS) {
|
|
@@ -1118,17 +1238,38 @@ function clampSectionFallback(text, degradations) {
|
|
|
1118
1238
|
reason: "Section text truncated to 3000 chars",
|
|
1119
1239
|
path: "root"
|
|
1120
1240
|
});
|
|
1121
|
-
return text
|
|
1241
|
+
return sliceSurrogateSafe(text, SECTION_TEXT_MAX_CHARS);
|
|
1122
1242
|
}
|
|
1123
|
-
function collectRefs(tree) {
|
|
1243
|
+
function collectRefs(tree, degradations) {
|
|
1124
1244
|
const definitions = /* @__PURE__ */ new Map();
|
|
1125
1245
|
const footnotes = /* @__PURE__ */ new Map();
|
|
1126
1246
|
for (const node of tree.children) {
|
|
1127
1247
|
if (node.type === "definition") {
|
|
1128
|
-
|
|
1248
|
+
const key = node.identifier.toLowerCase();
|
|
1249
|
+
if (definitions.has(key)) {
|
|
1250
|
+
degradations.add({
|
|
1251
|
+
nodeType: "definition",
|
|
1252
|
+
from: node.identifier,
|
|
1253
|
+
to: "ignored",
|
|
1254
|
+
reason: "Duplicate link reference definition; first definition wins",
|
|
1255
|
+
path: "root"
|
|
1256
|
+
});
|
|
1257
|
+
} else {
|
|
1258
|
+
definitions.set(key, node);
|
|
1259
|
+
}
|
|
1129
1260
|
}
|
|
1130
1261
|
if (node.type === "footnoteDefinition") {
|
|
1131
|
-
footnotes.
|
|
1262
|
+
if (footnotes.has(node.identifier)) {
|
|
1263
|
+
degradations.add({
|
|
1264
|
+
nodeType: "footnoteDefinition",
|
|
1265
|
+
from: node.identifier,
|
|
1266
|
+
to: "ignored",
|
|
1267
|
+
reason: "Duplicate footnote definition; first definition wins",
|
|
1268
|
+
path: "root"
|
|
1269
|
+
});
|
|
1270
|
+
} else {
|
|
1271
|
+
footnotes.set(node.identifier, node);
|
|
1272
|
+
}
|
|
1132
1273
|
}
|
|
1133
1274
|
}
|
|
1134
1275
|
const footnoteOrder = [];
|
|
@@ -1139,8 +1280,46 @@ function collectRefs(tree) {
|
|
|
1139
1280
|
footnoteOrder.push(id);
|
|
1140
1281
|
}
|
|
1141
1282
|
});
|
|
1283
|
+
for (let i = 0; i < footnoteOrder.length; i += 1) {
|
|
1284
|
+
const def = footnotes.get(footnoteOrder[i] ?? "");
|
|
1285
|
+
if (def === void 0) {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
for (const child of def.children) {
|
|
1289
|
+
walkFootnoteReferences(child, (id) => {
|
|
1290
|
+
if (!seen.has(id)) {
|
|
1291
|
+
seen.add(id);
|
|
1292
|
+
footnoteOrder.push(id);
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1142
1297
|
return { definitions, footnotes, footnoteOrder };
|
|
1143
1298
|
}
|
|
1299
|
+
function footnoteBodyText(node, numbers) {
|
|
1300
|
+
if (node === null || typeof node !== "object") {
|
|
1301
|
+
return "";
|
|
1302
|
+
}
|
|
1303
|
+
const record = node;
|
|
1304
|
+
if (record.type === "footnoteReference" && typeof record.identifier === "string") {
|
|
1305
|
+
const n = numbers.get(record.identifier);
|
|
1306
|
+
return n !== void 0 ? `[${String(n)}]` : `[^${record.identifier}]`;
|
|
1307
|
+
}
|
|
1308
|
+
if (typeof record.value === "string") {
|
|
1309
|
+
return record.value;
|
|
1310
|
+
}
|
|
1311
|
+
if (typeof record.alt === "string") {
|
|
1312
|
+
return record.alt;
|
|
1313
|
+
}
|
|
1314
|
+
if (record.children !== void 0) {
|
|
1315
|
+
const parts = [];
|
|
1316
|
+
for (const child of record.children) {
|
|
1317
|
+
parts.push(footnoteBodyText(child, numbers));
|
|
1318
|
+
}
|
|
1319
|
+
return parts.join("");
|
|
1320
|
+
}
|
|
1321
|
+
return "";
|
|
1322
|
+
}
|
|
1144
1323
|
function walkFootnoteReferences(node, onRef) {
|
|
1145
1324
|
if (node === null || typeof node !== "object") {
|
|
1146
1325
|
return;
|
|
@@ -1164,8 +1343,11 @@ function chunkMarkdown(text, max) {
|
|
|
1164
1343
|
return [text.length > 0 ? text : " "];
|
|
1165
1344
|
}
|
|
1166
1345
|
const out = [];
|
|
1167
|
-
|
|
1168
|
-
|
|
1346
|
+
let i = 0;
|
|
1347
|
+
while (i < text.length) {
|
|
1348
|
+
const end = surrogateSafeEnd(text, i + max);
|
|
1349
|
+
out.push(text.slice(i, end));
|
|
1350
|
+
i = end;
|
|
1169
1351
|
}
|
|
1170
1352
|
return out;
|
|
1171
1353
|
}
|
|
@@ -1175,45 +1357,6 @@ import { toString as toString3 } from "mdast-util-to-string";
|
|
|
1175
1357
|
|
|
1176
1358
|
// src/adapters/inline.ts
|
|
1177
1359
|
import { toString as toString2 } from "mdast-util-to-string";
|
|
1178
|
-
var EMOJI_SHORTCODE_RE = /:([a-z0-9_+-]{1,64}):/g;
|
|
1179
|
-
var KNOWN_SLACK_EMOJI = /* @__PURE__ */ new Set([
|
|
1180
|
-
"smile",
|
|
1181
|
-
"grinning",
|
|
1182
|
-
"wink",
|
|
1183
|
-
"blush",
|
|
1184
|
-
"wave",
|
|
1185
|
-
"thumbsup",
|
|
1186
|
-
"thumbsdown",
|
|
1187
|
-
"tada",
|
|
1188
|
-
"rocket",
|
|
1189
|
-
"fire",
|
|
1190
|
-
"heart",
|
|
1191
|
-
"eyes",
|
|
1192
|
-
"thinking_face",
|
|
1193
|
-
"sweat_smile",
|
|
1194
|
-
"joy",
|
|
1195
|
-
"sob",
|
|
1196
|
-
"clap",
|
|
1197
|
-
"100",
|
|
1198
|
-
"white_check_mark",
|
|
1199
|
-
"x",
|
|
1200
|
-
"warning",
|
|
1201
|
-
"bulb",
|
|
1202
|
-
"memo",
|
|
1203
|
-
"mag",
|
|
1204
|
-
"link",
|
|
1205
|
-
"gear",
|
|
1206
|
-
"bug",
|
|
1207
|
-
"sparkles",
|
|
1208
|
-
"+1",
|
|
1209
|
-
"-1",
|
|
1210
|
-
"ok_hand",
|
|
1211
|
-
"pray",
|
|
1212
|
-
"star",
|
|
1213
|
-
"zap",
|
|
1214
|
-
"check",
|
|
1215
|
-
"heavy_check_mark"
|
|
1216
|
-
]);
|
|
1217
1360
|
var HTTP_URL_RE = /^(https?:|mailto:)/i;
|
|
1218
1361
|
var IMAGE_EXT_RE = /\.(png|jpe?g|gif)(?:\?|#|$)/i;
|
|
1219
1362
|
function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
@@ -1221,26 +1364,24 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1221
1364
|
const hoistedImages = [];
|
|
1222
1365
|
const work = [...nodes];
|
|
1223
1366
|
let pendingAnchor;
|
|
1367
|
+
const htmlStyles = [];
|
|
1368
|
+
const activeStyle = () => htmlStyles[htmlStyles.length - 1] ?? baseStyle;
|
|
1224
1369
|
const flushPendingAnchor = () => {
|
|
1225
1370
|
if (pendingAnchor === void 0) {
|
|
1226
1371
|
return;
|
|
1227
1372
|
}
|
|
1228
|
-
|
|
1229
|
-
elements.push({
|
|
1230
|
-
type: "link",
|
|
1231
|
-
url: pendingAnchor.href,
|
|
1232
|
-
text: label.length > 0 ? label : pendingAnchor.href,
|
|
1233
|
-
style: styleOrUndefined(pendingAnchor.style)
|
|
1234
|
-
});
|
|
1373
|
+
elements.push(anchorInline(pendingAnchor, ctx));
|
|
1235
1374
|
pendingAnchor = void 0;
|
|
1236
1375
|
};
|
|
1237
1376
|
for (let i = 0; i < work.length; i += 1) {
|
|
1238
1377
|
const node = work[i];
|
|
1239
1378
|
if (pendingAnchor === void 0) {
|
|
1240
|
-
const reassembled = reassembleSlackLabeledLink(work, i,
|
|
1379
|
+
const reassembled = reassembleSlackLabeledLink(work, i, activeStyle());
|
|
1241
1380
|
if (reassembled !== void 0) {
|
|
1242
1381
|
if (reassembled.leadingText.value.length > 0) {
|
|
1243
|
-
elements.push(
|
|
1382
|
+
elements.push(
|
|
1383
|
+
...emitTextWithMentionsAndEmoji(reassembled.leadingText, activeStyle(), ctx)
|
|
1384
|
+
);
|
|
1244
1385
|
}
|
|
1245
1386
|
elements.push(reassembled.link);
|
|
1246
1387
|
if (reassembled.trailingText.value.length > 0) {
|
|
@@ -1270,7 +1411,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1270
1411
|
}
|
|
1271
1412
|
if (node.type === "html") {
|
|
1272
1413
|
const trimmed = node.value.trim();
|
|
1273
|
-
const entityInlines = slackEntityRichTextInlines(trimmed,
|
|
1414
|
+
const entityInlines = slackEntityRichTextInlines(trimmed, activeStyle());
|
|
1274
1415
|
if (entityInlines !== void 0) {
|
|
1275
1416
|
elements.push(...entityInlines);
|
|
1276
1417
|
continue;
|
|
@@ -1279,28 +1420,45 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1279
1420
|
if (openOnly !== null) {
|
|
1280
1421
|
const hrefMatch = /href\s*=\s*["']([^"']{0,3000})["']/i.exec(openOnly[1] ?? "");
|
|
1281
1422
|
const href = hrefMatch?.[1];
|
|
1282
|
-
if (href !== void 0
|
|
1283
|
-
pendingAnchor = {
|
|
1423
|
+
if (href !== void 0) {
|
|
1424
|
+
pendingAnchor = {
|
|
1425
|
+
href,
|
|
1426
|
+
style: activeStyle(),
|
|
1427
|
+
textParts: [],
|
|
1428
|
+
valid: isValidLinkUrl(href)
|
|
1429
|
+
};
|
|
1284
1430
|
continue;
|
|
1285
1431
|
}
|
|
1286
1432
|
}
|
|
1287
|
-
|
|
1433
|
+
const openTag = HTML_STYLE_OPEN_RE.exec(trimmed);
|
|
1434
|
+
if (openTag !== null) {
|
|
1435
|
+
htmlStyles.push(htmlTagStyle(activeStyle(), openTag[1] ?? ""));
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (HTML_STYLE_CLOSE_RE.test(trimmed)) {
|
|
1439
|
+
htmlStyles.pop();
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
elements.push(...emitInlineHtml(node.value, activeStyle(), ctx));
|
|
1288
1443
|
continue;
|
|
1289
1444
|
}
|
|
1290
1445
|
switch (node.type) {
|
|
1291
1446
|
case "text": {
|
|
1292
|
-
elements.push(...emitTextWithMentionsAndEmoji(node,
|
|
1447
|
+
elements.push(...emitTextWithMentionsAndEmoji(node, activeStyle(), ctx));
|
|
1293
1448
|
break;
|
|
1294
1449
|
}
|
|
1295
1450
|
case "strong": {
|
|
1296
|
-
const inner = emitPhrasing(node.children, ctx, {
|
|
1451
|
+
const inner = emitPhrasing(node.children, ctx, {
|
|
1452
|
+
...activeStyle(),
|
|
1453
|
+
bold: true
|
|
1454
|
+
});
|
|
1297
1455
|
elements.push(...inner.elements);
|
|
1298
1456
|
hoistedImages.push(...inner.hoistedImages);
|
|
1299
1457
|
break;
|
|
1300
1458
|
}
|
|
1301
1459
|
case "emphasis": {
|
|
1302
1460
|
const inner = emitPhrasing(node.children, ctx, {
|
|
1303
|
-
...
|
|
1461
|
+
...activeStyle(),
|
|
1304
1462
|
italic: true
|
|
1305
1463
|
});
|
|
1306
1464
|
elements.push(...inner.elements);
|
|
@@ -1308,7 +1466,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1308
1466
|
break;
|
|
1309
1467
|
}
|
|
1310
1468
|
case "delete": {
|
|
1311
|
-
const inner = emitPhrasing(node.children, ctx, { ...
|
|
1469
|
+
const inner = emitPhrasing(node.children, ctx, { ...activeStyle(), strike: true });
|
|
1312
1470
|
elements.push(...inner.elements);
|
|
1313
1471
|
hoistedImages.push(...inner.hoistedImages);
|
|
1314
1472
|
break;
|
|
@@ -1317,21 +1475,21 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1317
1475
|
const el = {
|
|
1318
1476
|
type: "text",
|
|
1319
1477
|
text: node.value,
|
|
1320
|
-
style: styleOrUndefined({ ...
|
|
1478
|
+
style: styleOrUndefined({ ...activeStyle(), code: true })
|
|
1321
1479
|
};
|
|
1322
1480
|
elements.push(el);
|
|
1323
1481
|
break;
|
|
1324
1482
|
}
|
|
1325
1483
|
case "break": {
|
|
1326
|
-
elements.push({ type: "text", text: "\n", style: styleOrUndefined(
|
|
1484
|
+
elements.push({ type: "text", text: "\n", style: styleOrUndefined(activeStyle()) });
|
|
1327
1485
|
break;
|
|
1328
1486
|
}
|
|
1329
1487
|
case "link": {
|
|
1330
|
-
elements.push(...emitLink(node, ctx,
|
|
1488
|
+
elements.push(...emitLink(node, ctx, activeStyle()));
|
|
1331
1489
|
break;
|
|
1332
1490
|
}
|
|
1333
1491
|
case "linkReference": {
|
|
1334
|
-
elements.push(...emitLinkReference(node, ctx,
|
|
1492
|
+
elements.push(...emitLinkReference(node, ctx, activeStyle()));
|
|
1335
1493
|
break;
|
|
1336
1494
|
}
|
|
1337
1495
|
case "image": {
|
|
@@ -1346,22 +1504,33 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1346
1504
|
type: "link",
|
|
1347
1505
|
url,
|
|
1348
1506
|
text: node.alt && node.alt.length > 0 ? node.alt : url,
|
|
1349
|
-
style: styleOrUndefined(
|
|
1507
|
+
style: styleOrUndefined(activeStyle())
|
|
1350
1508
|
});
|
|
1351
1509
|
} else {
|
|
1510
|
+
const linkable = isValidLinkUrl(url);
|
|
1352
1511
|
ctx.degradations.add({
|
|
1353
1512
|
nodeType: "image",
|
|
1354
1513
|
from: "image",
|
|
1355
|
-
to: "link",
|
|
1514
|
+
to: linkable ? "link" : "text",
|
|
1356
1515
|
reason: "Image URL is not a public http(s) png/jpg/gif \u22643000 chars",
|
|
1357
1516
|
path: ctx.path
|
|
1358
1517
|
});
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1518
|
+
const label = node.alt && node.alt.length > 0 ? node.alt : url.length > 0 ? url : "image";
|
|
1519
|
+
if (linkable) {
|
|
1520
|
+
elements.push({
|
|
1521
|
+
type: "link",
|
|
1522
|
+
url,
|
|
1523
|
+
text: label,
|
|
1524
|
+
style: styleOrUndefined(activeStyle())
|
|
1525
|
+
});
|
|
1526
|
+
} else {
|
|
1527
|
+
const suffix = url.length > 0 && url !== label ? ` (${url})` : "";
|
|
1528
|
+
elements.push({
|
|
1529
|
+
type: "text",
|
|
1530
|
+
text: `${label}${suffix}`,
|
|
1531
|
+
style: styleOrUndefined(activeStyle())
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1365
1534
|
}
|
|
1366
1535
|
break;
|
|
1367
1536
|
}
|
|
@@ -1371,7 +1540,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1371
1540
|
elements.push({
|
|
1372
1541
|
type: "text",
|
|
1373
1542
|
text: node.alt ?? node.identifier,
|
|
1374
|
-
style: styleOrUndefined(
|
|
1543
|
+
style: styleOrUndefined(activeStyle())
|
|
1375
1544
|
});
|
|
1376
1545
|
break;
|
|
1377
1546
|
}
|
|
@@ -1385,7 +1554,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1385
1554
|
}
|
|
1386
1555
|
],
|
|
1387
1556
|
ctx,
|
|
1388
|
-
|
|
1557
|
+
activeStyle()
|
|
1389
1558
|
);
|
|
1390
1559
|
elements.push(...asImage.elements);
|
|
1391
1560
|
hoistedImages.push(...asImage.hoistedImages);
|
|
@@ -1397,7 +1566,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1397
1566
|
elements.push({
|
|
1398
1567
|
type: "text",
|
|
1399
1568
|
text: `[${String(n)}]`,
|
|
1400
|
-
style: styleOrUndefined(
|
|
1569
|
+
style: styleOrUndefined(activeStyle())
|
|
1401
1570
|
});
|
|
1402
1571
|
break;
|
|
1403
1572
|
}
|
|
@@ -1405,13 +1574,13 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1405
1574
|
elements.push({
|
|
1406
1575
|
type: "text",
|
|
1407
1576
|
text: node.value,
|
|
1408
|
-
style: styleOrUndefined({ ...
|
|
1577
|
+
style: styleOrUndefined({ ...activeStyle(), code: true })
|
|
1409
1578
|
});
|
|
1410
1579
|
ctx.degradations.add({
|
|
1411
1580
|
nodeType: "inlineMath",
|
|
1412
1581
|
from: "inlineMath",
|
|
1413
1582
|
to: "style.code",
|
|
1414
|
-
reason: "Math
|
|
1583
|
+
reason: "Math is emitted as preformatted/code text",
|
|
1415
1584
|
path: ctx.path
|
|
1416
1585
|
});
|
|
1417
1586
|
break;
|
|
@@ -1419,7 +1588,7 @@ function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
|
1419
1588
|
default: {
|
|
1420
1589
|
const text = toString2(node);
|
|
1421
1590
|
if (text.length > 0) {
|
|
1422
|
-
elements.push({ type: "text", text, style: styleOrUndefined(
|
|
1591
|
+
elements.push({ type: "text", text, style: styleOrUndefined(activeStyle()) });
|
|
1423
1592
|
}
|
|
1424
1593
|
break;
|
|
1425
1594
|
}
|
|
@@ -1488,7 +1657,7 @@ function truncateLabel(label, max) {
|
|
|
1488
1657
|
if (label.length <= max) {
|
|
1489
1658
|
return { text: label, truncated: false };
|
|
1490
1659
|
}
|
|
1491
|
-
return { text: `${label
|
|
1660
|
+
return { text: `${sliceSurrogateSafe(label, Math.max(0, max - 1))}${ELLIPSIS}`, truncated: true };
|
|
1492
1661
|
}
|
|
1493
1662
|
function hoistedImageBlock(h, ctx) {
|
|
1494
1663
|
return {
|
|
@@ -1509,7 +1678,7 @@ function clampSectionText(text, ctx, nodeType) {
|
|
|
1509
1678
|
reason: "Section text truncated to 3000 chars",
|
|
1510
1679
|
path: ctx.path
|
|
1511
1680
|
});
|
|
1512
|
-
return text
|
|
1681
|
+
return sliceSurrogateSafe(text, SECTION_TEXT_MAX_CHARS);
|
|
1513
1682
|
}
|
|
1514
1683
|
function preformattedRichText(text, language) {
|
|
1515
1684
|
return {
|
|
@@ -1535,7 +1704,7 @@ function clampImageText(value, ctx, field) {
|
|
|
1535
1704
|
reason: `Image ${field} truncated to ${String(IMAGE_ALT_MAX_CHARS)} chars`,
|
|
1536
1705
|
path: ctx.path
|
|
1537
1706
|
});
|
|
1538
|
-
return value
|
|
1707
|
+
return sliceSurrogateSafe(value, IMAGE_ALT_MAX_CHARS);
|
|
1539
1708
|
}
|
|
1540
1709
|
function styleOrUndefined(style) {
|
|
1541
1710
|
if (style.bold === true || style.italic === true || style.strike === true || style.code === true || style.underline === true) {
|
|
@@ -1543,13 +1712,21 @@ function styleOrUndefined(style) {
|
|
|
1543
1712
|
}
|
|
1544
1713
|
return void 0;
|
|
1545
1714
|
}
|
|
1546
|
-
var
|
|
1715
|
+
var SLACK_ENTITY_AT_RE = /<([@#!][^<>]{1,340}|(?:https?:|mailto:)[^<>|]{1,3000}\|[^<>]{1,300})>/iy;
|
|
1716
|
+
var CHAR_LT = 60;
|
|
1717
|
+
var CHAR_HASH = 35;
|
|
1718
|
+
var CHAR_AT = 64;
|
|
1719
|
+
var CHAR_COLON = 58;
|
|
1547
1720
|
var SLACK_USER_RE = /^@([UW][A-Z0-9]{1,60})(?:\|[^>]{0,300})?$/;
|
|
1548
1721
|
var SLACK_CHANNEL_RE = /^#([CDG][A-Z0-9]{1,60})(?:\|[^>]{0,300})?$/;
|
|
1549
1722
|
var SLACK_USERGROUP_RE = /^!subteam\^(S[A-Z0-9]{1,60})(?:\|[^>]{0,300})?$/;
|
|
1550
1723
|
var SLACK_BROADCAST_RE = /^!(here|channel|everyone)(?:\|[^>]{0,300})?$/;
|
|
1551
1724
|
var SLACK_DATE_RE = /^!date\^(\d{1,13})\^([^^|]{1,300})(?:\^([^^|]{1,3000}))?(?:\|([^]{0,300}))?$/;
|
|
1552
1725
|
var SLACK_LINK_RE = /^((?:https?:|mailto:)[^|]{1,3000})\|([^]{1,300})$/i;
|
|
1726
|
+
var MENTION_PATTERN = String.raw`(?<![A-Za-z0-9._-])@([a-zA-Z0-9_-](?:[a-zA-Z0-9._-]{0,62}[a-zA-Z0-9_-])?)|(?<![A-Za-z0-9_-])#([a-zA-Z0-9_-]{1,80})`;
|
|
1727
|
+
var MENTION_RE = new RegExp(MENTION_PATTERN, "g");
|
|
1728
|
+
var MENTION_AT_RE = new RegExp(MENTION_PATTERN, "y");
|
|
1729
|
+
var EMOJI_AT_RE = /(?<![A-Za-z0-9]):([a-z0-9_+-]{1,64}):(?![A-Za-z0-9])/y;
|
|
1553
1730
|
function slackEntityElement(inner, style) {
|
|
1554
1731
|
const labeledLink = SLACK_LINK_RE.exec(inner);
|
|
1555
1732
|
if (labeledLink?.[1] !== void 0 && labeledLink[2] !== void 0) {
|
|
@@ -1598,39 +1775,79 @@ function slackEntityElement(inner, style) {
|
|
|
1598
1775
|
}
|
|
1599
1776
|
return void 0;
|
|
1600
1777
|
}
|
|
1601
|
-
function
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
if (match === null) {
|
|
1605
|
-
return void 0;
|
|
1606
|
-
}
|
|
1778
|
+
function scanTextRun(value, style, resolveUser, resolveChannel) {
|
|
1779
|
+
const scanMentions = resolveUser !== void 0 || resolveChannel !== void 0;
|
|
1780
|
+
const styleOpt = styleOrUndefined(style);
|
|
1607
1781
|
const out = [];
|
|
1608
|
-
let last = 0;
|
|
1609
1782
|
let sawEntity = false;
|
|
1610
|
-
|
|
1611
|
-
|
|
1783
|
+
let i = 0;
|
|
1784
|
+
let textStart = 0;
|
|
1785
|
+
while (i < value.length) {
|
|
1786
|
+
const ch = value.charCodeAt(i);
|
|
1787
|
+
let element;
|
|
1788
|
+
let tokenEnd = 0;
|
|
1789
|
+
if (ch === CHAR_LT) {
|
|
1790
|
+
SLACK_ENTITY_AT_RE.lastIndex = i;
|
|
1791
|
+
const match = SLACK_ENTITY_AT_RE.exec(value);
|
|
1792
|
+
if (match !== null) {
|
|
1793
|
+
element = slackEntityElement(match[1] ?? "", style);
|
|
1794
|
+
if (element !== void 0) {
|
|
1795
|
+
tokenEnd = SLACK_ENTITY_AT_RE.lastIndex;
|
|
1796
|
+
sawEntity = true;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
} else if (scanMentions && (ch === CHAR_AT || ch === CHAR_HASH)) {
|
|
1800
|
+
MENTION_AT_RE.lastIndex = i;
|
|
1801
|
+
const match = MENTION_AT_RE.exec(value);
|
|
1802
|
+
if (match !== null) {
|
|
1803
|
+
const userHandle = match[1];
|
|
1804
|
+
const channelName = match[2];
|
|
1805
|
+
if (userHandle !== void 0 && resolveUser !== void 0) {
|
|
1806
|
+
const userId = resolveUser(userHandle);
|
|
1807
|
+
if (userId !== void 0 && userId.length > 0) {
|
|
1808
|
+
element = { type: "user", user_id: userId, style: styleOpt };
|
|
1809
|
+
tokenEnd = MENTION_AT_RE.lastIndex;
|
|
1810
|
+
}
|
|
1811
|
+
} else if (channelName !== void 0 && resolveChannel !== void 0) {
|
|
1812
|
+
const channelId = resolveChannel(channelName);
|
|
1813
|
+
if (channelId !== void 0 && channelId.length > 0) {
|
|
1814
|
+
element = { type: "channel", channel_id: channelId, style: styleOpt };
|
|
1815
|
+
tokenEnd = MENTION_AT_RE.lastIndex;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
} else if (ch === CHAR_COLON) {
|
|
1820
|
+
EMOJI_AT_RE.lastIndex = i;
|
|
1821
|
+
const match = EMOJI_AT_RE.exec(value);
|
|
1822
|
+
if (match !== null) {
|
|
1823
|
+
const emoji = { type: "emoji", name: match[1] ?? "" };
|
|
1824
|
+
element = emoji;
|
|
1825
|
+
tokenEnd = EMOJI_AT_RE.lastIndex;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1612
1828
|
if (element !== void 0) {
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
out.push(...emit(value.slice(last, match.index)));
|
|
1829
|
+
if (i > textStart) {
|
|
1830
|
+
out.push({ type: "text", text: value.slice(textStart, i), style: styleOpt });
|
|
1616
1831
|
}
|
|
1617
1832
|
out.push(element);
|
|
1618
|
-
|
|
1833
|
+
i = tokenEnd;
|
|
1834
|
+
textStart = i;
|
|
1835
|
+
continue;
|
|
1619
1836
|
}
|
|
1620
|
-
|
|
1837
|
+
i += 1;
|
|
1621
1838
|
}
|
|
1622
|
-
if (
|
|
1623
|
-
|
|
1839
|
+
if (textStart < value.length) {
|
|
1840
|
+
out.push({ type: "text", text: value.slice(textStart), style: styleOpt });
|
|
1624
1841
|
}
|
|
1625
|
-
if (
|
|
1626
|
-
out.push(
|
|
1842
|
+
if (out.length === 0) {
|
|
1843
|
+
out.push({ type: "text", text: value, style: styleOpt });
|
|
1627
1844
|
}
|
|
1628
|
-
return out;
|
|
1845
|
+
return { elements: out, sawEntity };
|
|
1629
1846
|
}
|
|
1630
1847
|
function slackEntityRichTextInlines(value, style) {
|
|
1631
|
-
|
|
1848
|
+
const { elements, sawEntity } = scanTextRun(value, style);
|
|
1849
|
+
return sawEntity ? elements : void 0;
|
|
1632
1850
|
}
|
|
1633
|
-
var MENTION_RE = /(?<![A-Za-z0-9._-])@([a-zA-Z0-9._-]{1,64})|(?<![A-Za-z0-9_-])#([a-zA-Z0-9_-]{1,80})/g;
|
|
1634
1851
|
function phrasingNeedsMentionResolution(nodes, ctx) {
|
|
1635
1852
|
const resolvers = ctx.config.mentionResolvers;
|
|
1636
1853
|
if (resolvers?.resolveUser === void 0 && resolvers?.resolveChannel === void 0) {
|
|
@@ -1641,89 +1858,8 @@ function phrasingNeedsMentionResolution(nodes, ctx) {
|
|
|
1641
1858
|
return MENTION_RE.test(plain);
|
|
1642
1859
|
}
|
|
1643
1860
|
function emitTextWithMentionsAndEmoji(node, style, ctx) {
|
|
1644
|
-
const viaEntities = slackEntitySplit(
|
|
1645
|
-
node.value,
|
|
1646
|
-
style,
|
|
1647
|
-
(slice) => emitMentionsAndEmoji(slice, style, ctx)
|
|
1648
|
-
);
|
|
1649
|
-
if (viaEntities !== void 0) {
|
|
1650
|
-
return viaEntities;
|
|
1651
|
-
}
|
|
1652
|
-
return emitMentionsAndEmoji(node.value, style, ctx);
|
|
1653
|
-
}
|
|
1654
|
-
function emitMentionsAndEmoji(value, style, ctx) {
|
|
1655
1861
|
const resolvers = ctx.config.mentionResolvers;
|
|
1656
|
-
|
|
1657
|
-
const canChannel = resolvers?.resolveChannel !== void 0;
|
|
1658
|
-
if (!canUser && !canChannel) {
|
|
1659
|
-
return emitTextWithEmoji(value, style);
|
|
1660
|
-
}
|
|
1661
|
-
const out = [];
|
|
1662
|
-
MENTION_RE.lastIndex = 0;
|
|
1663
|
-
let last = 0;
|
|
1664
|
-
let match = MENTION_RE.exec(value);
|
|
1665
|
-
while (match !== null) {
|
|
1666
|
-
const start = match.index;
|
|
1667
|
-
if (start > last) {
|
|
1668
|
-
out.push(...emitTextWithEmoji(value.slice(last, start), style));
|
|
1669
|
-
}
|
|
1670
|
-
const userHandle = match[1];
|
|
1671
|
-
const channelName = match[2];
|
|
1672
|
-
if (userHandle !== void 0 && canUser) {
|
|
1673
|
-
const userId = resolvers.resolveUser?.(userHandle);
|
|
1674
|
-
if (userId !== void 0 && userId.length > 0) {
|
|
1675
|
-
out.push({ type: "user", user_id: userId, style: styleOrUndefined(style) });
|
|
1676
|
-
} else {
|
|
1677
|
-
out.push(...emitTextWithEmoji(match[0], style));
|
|
1678
|
-
}
|
|
1679
|
-
} else if (channelName !== void 0 && canChannel) {
|
|
1680
|
-
const channelId = resolvers.resolveChannel?.(channelName);
|
|
1681
|
-
if (channelId !== void 0 && channelId.length > 0) {
|
|
1682
|
-
out.push({ type: "channel", channel_id: channelId, style: styleOrUndefined(style) });
|
|
1683
|
-
} else {
|
|
1684
|
-
out.push(...emitTextWithEmoji(match[0], style));
|
|
1685
|
-
}
|
|
1686
|
-
} else {
|
|
1687
|
-
out.push(...emitTextWithEmoji(match[0], style));
|
|
1688
|
-
}
|
|
1689
|
-
last = start + match[0].length;
|
|
1690
|
-
match = MENTION_RE.exec(value);
|
|
1691
|
-
}
|
|
1692
|
-
if (last < value.length) {
|
|
1693
|
-
out.push(...emitTextWithEmoji(value.slice(last), style));
|
|
1694
|
-
}
|
|
1695
|
-
if (out.length === 0) {
|
|
1696
|
-
out.push(...emitTextWithEmoji(value, style));
|
|
1697
|
-
}
|
|
1698
|
-
return out;
|
|
1699
|
-
}
|
|
1700
|
-
function emitTextWithEmoji(value, style) {
|
|
1701
|
-
const out = [];
|
|
1702
|
-
EMOJI_SHORTCODE_RE.lastIndex = 0;
|
|
1703
|
-
let last = 0;
|
|
1704
|
-
let match = EMOJI_SHORTCODE_RE.exec(value);
|
|
1705
|
-
while (match !== null) {
|
|
1706
|
-
const start = match.index;
|
|
1707
|
-
if (start > last) {
|
|
1708
|
-
out.push({ type: "text", text: value.slice(last, start), style: styleOrUndefined(style) });
|
|
1709
|
-
}
|
|
1710
|
-
const name = match[1] ?? "";
|
|
1711
|
-
if (KNOWN_SLACK_EMOJI.has(name)) {
|
|
1712
|
-
const emoji = { type: "emoji", name };
|
|
1713
|
-
out.push(emoji);
|
|
1714
|
-
} else {
|
|
1715
|
-
out.push({ type: "text", text: match[0], style: styleOrUndefined(style) });
|
|
1716
|
-
}
|
|
1717
|
-
last = start + match[0].length;
|
|
1718
|
-
match = EMOJI_SHORTCODE_RE.exec(value);
|
|
1719
|
-
}
|
|
1720
|
-
if (last < value.length) {
|
|
1721
|
-
out.push({ type: "text", text: value.slice(last), style: styleOrUndefined(style) });
|
|
1722
|
-
}
|
|
1723
|
-
if (out.length === 0) {
|
|
1724
|
-
out.push({ type: "text", text: value, style: styleOrUndefined(style) });
|
|
1725
|
-
}
|
|
1726
|
-
return out;
|
|
1862
|
+
return scanTextRun(node.value, style, resolvers?.resolveUser, resolvers?.resolveChannel).elements;
|
|
1727
1863
|
}
|
|
1728
1864
|
function reassembleSlackLabeledLink(work, i, style) {
|
|
1729
1865
|
const lead = work[i];
|
|
@@ -1780,7 +1916,7 @@ function emitLink(node, ctx, style) {
|
|
|
1780
1916
|
reason: "Invalid or oversized URL",
|
|
1781
1917
|
path: ctx.path
|
|
1782
1918
|
});
|
|
1783
|
-
const suffix = url.length > 0 ? ` (${url})` : "";
|
|
1919
|
+
const suffix = url.length > 0 && url !== text ? ` (${url})` : "";
|
|
1784
1920
|
return [{ type: "text", text: `${text}${suffix}`, style: styleOrUndefined(style) }];
|
|
1785
1921
|
}
|
|
1786
1922
|
const link = {
|
|
@@ -1817,9 +1953,45 @@ function isValidLinkUrl(url) {
|
|
|
1817
1953
|
if (url.length === 0 || url.length > IMAGE_URL_MAX_CHARS) {
|
|
1818
1954
|
return false;
|
|
1819
1955
|
}
|
|
1820
|
-
return HTTP_URL_RE.test(url)
|
|
1956
|
+
return HTTP_URL_RE.test(url);
|
|
1957
|
+
}
|
|
1958
|
+
function anchorInline(anchor, ctx) {
|
|
1959
|
+
const label = anchor.textParts.join("");
|
|
1960
|
+
if (anchor.valid) {
|
|
1961
|
+
return {
|
|
1962
|
+
type: "link",
|
|
1963
|
+
url: anchor.href,
|
|
1964
|
+
text: label.length > 0 ? label : anchor.href,
|
|
1965
|
+
style: styleOrUndefined(anchor.style)
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
ctx.degradations.add({
|
|
1969
|
+
nodeType: "link",
|
|
1970
|
+
from: "link",
|
|
1971
|
+
to: "text",
|
|
1972
|
+
reason: "Invalid or oversized URL",
|
|
1973
|
+
path: ctx.path
|
|
1974
|
+
});
|
|
1975
|
+
const base = label.length > 0 ? label : anchor.href;
|
|
1976
|
+
const suffix = label.length > 0 && anchor.href.length > 0 && anchor.href !== label ? ` (${anchor.href})` : "";
|
|
1977
|
+
return { type: "text", text: `${base}${suffix}`, style: styleOrUndefined(anchor.style) };
|
|
1978
|
+
}
|
|
1979
|
+
var HTML_STYLE_OPEN_RE = /^<(b|strong|i|em|s|del|code)(\s+[^>]{0,200})?>$/i;
|
|
1980
|
+
var HTML_STYLE_CLOSE_RE = /^<\/(b|strong|i|em|s|del|code)\s*>$/i;
|
|
1981
|
+
function htmlTagStyle(base, tag) {
|
|
1982
|
+
const t = tag.toLowerCase();
|
|
1983
|
+
if (t === "b" || t === "strong") {
|
|
1984
|
+
return { ...base, bold: true };
|
|
1985
|
+
}
|
|
1986
|
+
if (t === "i" || t === "em") {
|
|
1987
|
+
return { ...base, italic: true };
|
|
1988
|
+
}
|
|
1989
|
+
if (t === "s" || t === "del") {
|
|
1990
|
+
return { ...base, strike: true };
|
|
1991
|
+
}
|
|
1992
|
+
return { ...base, code: true };
|
|
1821
1993
|
}
|
|
1822
|
-
var INLINE_TAG_RE = /<\/?(b|strong|i|em|s|del|code|br|a)(\s+[^>]{0,200})
|
|
1994
|
+
var INLINE_TAG_RE = /<\/?(b|strong|i|em|s|del|code|br|a)(\s+[^>]{0,200})?\s*\/?>|<[^>]{0,200}>|[^<]+|</gi;
|
|
1823
1995
|
function emitInlineHtml(html, style, ctx) {
|
|
1824
1996
|
const out = [];
|
|
1825
1997
|
const stack = [];
|
|
@@ -1827,11 +1999,15 @@ function emitInlineHtml(html, style, ctx) {
|
|
|
1827
1999
|
let openAnchor;
|
|
1828
2000
|
INLINE_TAG_RE.lastIndex = 0;
|
|
1829
2001
|
let m = INLINE_TAG_RE.exec(html);
|
|
1830
|
-
let
|
|
2002
|
+
let strippedUnknown = false;
|
|
1831
2003
|
while (m !== null) {
|
|
1832
|
-
matched = true;
|
|
1833
2004
|
const token = m[0];
|
|
1834
|
-
if (token.startsWith("<")) {
|
|
2005
|
+
if (token.startsWith("<") && token.length > 1 && m[1] === void 0) {
|
|
2006
|
+
strippedUnknown = true;
|
|
2007
|
+
m = INLINE_TAG_RE.exec(html);
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
if (token.startsWith("<") && m[1] !== void 0) {
|
|
1835
2011
|
const tag = (m[1] ?? "").toLowerCase();
|
|
1836
2012
|
const closing = token.startsWith("</");
|
|
1837
2013
|
if (tag === "br") {
|
|
@@ -1842,13 +2018,7 @@ function emitInlineHtml(html, style, ctx) {
|
|
|
1842
2018
|
}
|
|
1843
2019
|
} else if (closing) {
|
|
1844
2020
|
if (tag === "a" && openAnchor !== void 0) {
|
|
1845
|
-
|
|
1846
|
-
out.push({
|
|
1847
|
-
type: "link",
|
|
1848
|
-
url: openAnchor.href,
|
|
1849
|
-
text: label.length > 0 ? label : openAnchor.href,
|
|
1850
|
-
style: styleOrUndefined(openAnchor.style)
|
|
1851
|
-
});
|
|
2021
|
+
out.push(anchorInline(openAnchor, ctx));
|
|
1852
2022
|
openAnchor = void 0;
|
|
1853
2023
|
}
|
|
1854
2024
|
stack.pop();
|
|
@@ -1857,8 +2027,11 @@ function emitInlineHtml(html, style, ctx) {
|
|
|
1857
2027
|
const hrefMatch = /href\s*=\s*["']([^"']{0,3000})["']/i.exec(token);
|
|
1858
2028
|
const href = hrefMatch?.[1];
|
|
1859
2029
|
stack.push({ tag, style: current });
|
|
1860
|
-
if (href !== void 0
|
|
1861
|
-
openAnchor
|
|
2030
|
+
if (href !== void 0) {
|
|
2031
|
+
if (openAnchor !== void 0) {
|
|
2032
|
+
out.push(anchorInline(openAnchor, ctx));
|
|
2033
|
+
}
|
|
2034
|
+
openAnchor = { href, style: current, textParts: [], valid: isValidLinkUrl(href) };
|
|
1862
2035
|
}
|
|
1863
2036
|
} else {
|
|
1864
2037
|
const next = { ...current };
|
|
@@ -1879,15 +2052,9 @@ function emitInlineHtml(html, style, ctx) {
|
|
|
1879
2052
|
m = INLINE_TAG_RE.exec(html);
|
|
1880
2053
|
}
|
|
1881
2054
|
if (openAnchor !== void 0) {
|
|
1882
|
-
|
|
1883
|
-
out.push({
|
|
1884
|
-
type: "link",
|
|
1885
|
-
url: openAnchor.href,
|
|
1886
|
-
text: label.length > 0 ? label : openAnchor.href,
|
|
1887
|
-
style: styleOrUndefined(openAnchor.style)
|
|
1888
|
-
});
|
|
2055
|
+
out.push(anchorInline(openAnchor, ctx));
|
|
1889
2056
|
}
|
|
1890
|
-
if (
|
|
2057
|
+
if (strippedUnknown) {
|
|
1891
2058
|
ctx.degradations.add({
|
|
1892
2059
|
nodeType: "html",
|
|
1893
2060
|
from: "html",
|
|
@@ -1895,10 +2062,6 @@ function emitInlineHtml(html, style, ctx) {
|
|
|
1895
2062
|
reason: "Unrecognized inline HTML stripped to text",
|
|
1896
2063
|
path: ctx.path
|
|
1897
2064
|
});
|
|
1898
|
-
const stripped = html.replace(/<[^>]{0,200}>/g, "");
|
|
1899
|
-
if (stripped.length > 0) {
|
|
1900
|
-
out.push({ type: "text", text: stripped, style: styleOrUndefined(style) });
|
|
1901
|
-
}
|
|
1902
2065
|
}
|
|
1903
2066
|
return out;
|
|
1904
2067
|
}
|
|
@@ -2130,10 +2293,13 @@ function emitPlainHeader(heading, withLevel) {
|
|
|
2130
2293
|
const block = {
|
|
2131
2294
|
type: "header",
|
|
2132
2295
|
text: { type: "plain_text", text: plain.length > 0 ? plain : " " },
|
|
2133
|
-
level: withLevel ? heading.depth : void 0
|
|
2296
|
+
level: withLevel ? headerLevelOf(heading.depth) : void 0
|
|
2134
2297
|
};
|
|
2135
2298
|
return [block];
|
|
2136
2299
|
}
|
|
2300
|
+
function headerLevelOf(depth) {
|
|
2301
|
+
return depth === 1 || depth === 2 || depth === 3 || depth === 4 ? depth : void 0;
|
|
2302
|
+
}
|
|
2137
2303
|
function emitRichHeading(heading, ctx, hoistImages) {
|
|
2138
2304
|
const plain = phrasingToPlain(heading.children);
|
|
2139
2305
|
const depth = heading.depth;
|
|
@@ -2296,7 +2462,7 @@ function clampContainerTitle(text, ctx) {
|
|
|
2296
2462
|
reason: `Container title truncated to ${String(CONTAINER_TITLE_MAX_CHARS)} chars`,
|
|
2297
2463
|
path: ctx.path
|
|
2298
2464
|
});
|
|
2299
|
-
return text
|
|
2465
|
+
return sliceSurrogateSafe(text, CONTAINER_TITLE_MAX_CHARS);
|
|
2300
2466
|
}
|
|
2301
2467
|
|
|
2302
2468
|
// src/adapters/list-adapter.ts
|
|
@@ -2376,27 +2542,51 @@ function emitListTree(list, indent, ctx) {
|
|
|
2376
2542
|
});
|
|
2377
2543
|
effectiveIndent = RICH_TEXT_LIST_INDENT_MAX;
|
|
2378
2544
|
}
|
|
2379
|
-
const
|
|
2380
|
-
const
|
|
2545
|
+
const ordered = list.ordered === true;
|
|
2546
|
+
const startOffset = ordered && list.start !== null && list.start !== void 0 && list.start > 1 ? list.start - 1 : 0;
|
|
2547
|
+
const out = [];
|
|
2381
2548
|
const hoistedImages = [];
|
|
2382
|
-
|
|
2549
|
+
let pendingSections = [];
|
|
2550
|
+
const flush = (lastIndex) => {
|
|
2551
|
+
if (pendingSections.length === 0) {
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
const chunkStartIndex = lastIndex - pendingSections.length + 1;
|
|
2555
|
+
const offset = startOffset + chunkStartIndex;
|
|
2556
|
+
out.push({
|
|
2557
|
+
type: "rich_text_list",
|
|
2558
|
+
style: ordered ? "ordered" : "bullet",
|
|
2559
|
+
elements: pendingSections,
|
|
2560
|
+
indent: effectiveIndent,
|
|
2561
|
+
offset: ordered && offset > 0 ? offset : void 0
|
|
2562
|
+
});
|
|
2563
|
+
pendingSections = [];
|
|
2564
|
+
};
|
|
2565
|
+
for (const [index, item] of list.children.entries()) {
|
|
2383
2566
|
const { section, childLists, hoistedImages: itemImages } = emitListItem(item, ctx);
|
|
2384
|
-
|
|
2567
|
+
pendingSections.push(section);
|
|
2385
2568
|
hoistedImages.push(...itemImages);
|
|
2569
|
+
if (childLists.length === 0) {
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
flush(index);
|
|
2386
2573
|
for (const child of childLists) {
|
|
2387
2574
|
const nestedResult = emitListTree(child, effectiveIndent + 1, ctx);
|
|
2388
|
-
|
|
2575
|
+
out.push(...nestedResult.elements);
|
|
2389
2576
|
hoistedImages.push(...nestedResult.hoistedImages);
|
|
2390
2577
|
}
|
|
2391
2578
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2579
|
+
flush(list.children.length - 1);
|
|
2580
|
+
if (out.length === 0) {
|
|
2581
|
+
out.push({
|
|
2582
|
+
type: "rich_text_list",
|
|
2583
|
+
style: ordered ? "ordered" : "bullet",
|
|
2584
|
+
elements: [{ type: "rich_text_section", elements: [{ type: "text", text: " " }] }],
|
|
2585
|
+
indent: effectiveIndent,
|
|
2586
|
+
offset: void 0
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
return { elements: out, hoistedImages };
|
|
2400
2590
|
}
|
|
2401
2591
|
function collectListItemPhrasing(item) {
|
|
2402
2592
|
const phrasing = [];
|
|
@@ -2489,7 +2679,7 @@ function preformattedMath(value, withLang, ctx) {
|
|
|
2489
2679
|
nodeType: "math",
|
|
2490
2680
|
from: "math",
|
|
2491
2681
|
to: "rich_text_preformatted",
|
|
2492
|
-
reason: "
|
|
2682
|
+
reason: "Math is emitted as preformatted/code text",
|
|
2493
2683
|
path: ctx.path
|
|
2494
2684
|
});
|
|
2495
2685
|
return preformattedRichText(value, withLang ? "latex" : void 0);
|
|
@@ -2713,7 +2903,19 @@ async function tryRenderers(source, ctx) {
|
|
|
2713
2903
|
if (!renderer.canRender(req)) {
|
|
2714
2904
|
continue;
|
|
2715
2905
|
}
|
|
2716
|
-
|
|
2906
|
+
let result;
|
|
2907
|
+
try {
|
|
2908
|
+
result = await renderer.render(req);
|
|
2909
|
+
} catch (err) {
|
|
2910
|
+
ctx.degradations.add({
|
|
2911
|
+
nodeType: "code.mermaid",
|
|
2912
|
+
from: "image.render",
|
|
2913
|
+
to: "skip",
|
|
2914
|
+
reason: `Renderer failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2915
|
+
path: ctx.path
|
|
2916
|
+
});
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2717
2919
|
if (result === null) {
|
|
2718
2920
|
continue;
|
|
2719
2921
|
}
|
|
@@ -2745,7 +2947,19 @@ async function tryRenderers(source, ctx) {
|
|
|
2745
2947
|
});
|
|
2746
2948
|
continue;
|
|
2747
2949
|
}
|
|
2748
|
-
|
|
2950
|
+
let uploaded;
|
|
2951
|
+
try {
|
|
2952
|
+
uploaded = await uploader.upload(result);
|
|
2953
|
+
} catch (err) {
|
|
2954
|
+
ctx.degradations.add({
|
|
2955
|
+
nodeType: "code.mermaid",
|
|
2956
|
+
from: "image.bytes",
|
|
2957
|
+
to: "skip",
|
|
2958
|
+
reason: `Slack upload failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2959
|
+
path: ctx.path
|
|
2960
|
+
});
|
|
2961
|
+
continue;
|
|
2962
|
+
}
|
|
2749
2963
|
return {
|
|
2750
2964
|
type: "image",
|
|
2751
2965
|
alt_text: clampImageText(result.altText, ctx, "alt_text"),
|
|
@@ -2808,11 +3022,11 @@ function emitParagraph(paragraph, emitCtx, loneImage, hoistImages) {
|
|
|
2808
3022
|
const img = loneImage;
|
|
2809
3023
|
if (isPublicImageUrl(img.url)) {
|
|
2810
3024
|
const altRaw = img.alt ?? "image";
|
|
2811
|
-
const
|
|
3025
|
+
const alt2 = clampImageText(altRaw.length > 0 ? altRaw : "image", emitCtx, "alt_text");
|
|
2812
3026
|
const block = {
|
|
2813
3027
|
type: "image",
|
|
2814
3028
|
image_url: img.url,
|
|
2815
|
-
alt_text:
|
|
3029
|
+
alt_text: alt2,
|
|
2816
3030
|
title: img.title !== null && img.title !== void 0 && img.title.length > 0 ? {
|
|
2817
3031
|
type: "plain_text",
|
|
2818
3032
|
text: clampImageText(img.title, emitCtx, "title")
|
|
@@ -2820,20 +3034,23 @@ function emitParagraph(paragraph, emitCtx, loneImage, hoistImages) {
|
|
|
2820
3034
|
};
|
|
2821
3035
|
return [block];
|
|
2822
3036
|
}
|
|
3037
|
+
const linkable = isValidLinkUrl(img.url);
|
|
2823
3038
|
emitCtx.degradations.add({
|
|
2824
3039
|
nodeType: "image",
|
|
2825
3040
|
from: "image",
|
|
2826
|
-
to: "section.link",
|
|
3041
|
+
to: linkable ? "section.link" : "section.text",
|
|
2827
3042
|
reason: "Non-public or invalid image URL",
|
|
2828
3043
|
path: emitCtx.path
|
|
2829
3044
|
});
|
|
2830
|
-
const
|
|
3045
|
+
const alt = img.alt ?? "";
|
|
3046
|
+
const label = alt.length > 0 ? alt : img.url.length > 0 ? img.url : "image";
|
|
3047
|
+
const fallbackText = linkable ? `<${img.url}|${label}>` : img.url.length > 0 && img.url !== label ? `${label} (${img.url})` : label;
|
|
2831
3048
|
return [
|
|
2832
3049
|
{
|
|
2833
3050
|
type: "section",
|
|
2834
3051
|
text: {
|
|
2835
3052
|
type: "mrkdwn",
|
|
2836
|
-
text: clampSectionText(
|
|
3053
|
+
text: clampSectionText(fallbackText, emitCtx, "section")
|
|
2837
3054
|
}
|
|
2838
3055
|
}
|
|
2839
3056
|
];
|
|
@@ -2893,14 +3110,15 @@ var TableAdapter = class {
|
|
|
2893
3110
|
return node.type === "table";
|
|
2894
3111
|
}
|
|
2895
3112
|
plan(node, ctx) {
|
|
2896
|
-
const table = node;
|
|
3113
|
+
const table = normalizeTableRows(node, ctx);
|
|
2897
3114
|
const rowCount = table.children.length;
|
|
2898
3115
|
const colCount = table.children[0]?.children.length ?? 0;
|
|
2899
|
-
const cellChars = countEmittedCellChars(table, ctx);
|
|
2900
3116
|
if (colCount > TABLE_MAX_COLS) {
|
|
3117
|
+
const cellChars = countEmittedCellChars(table, ctx, "table");
|
|
2901
3118
|
return markdownThenAscii(table, cellChars, "Table exceeds 20 columns");
|
|
2902
3119
|
}
|
|
2903
3120
|
if (rowCount <= TABLE_PREFERRED_MAX_ROWS) {
|
|
3121
|
+
const cellChars = countEmittedCellChars(table, ctx, "table");
|
|
2904
3122
|
return [
|
|
2905
3123
|
{
|
|
2906
3124
|
requires: ["tableBlock"],
|
|
@@ -2915,11 +3133,12 @@ var TableAdapter = class {
|
|
|
2915
3133
|
const dataRows = Math.max(0, rowCount - 1);
|
|
2916
3134
|
if (dataRows <= DATA_TABLE_MAX_DATA_ROWS) {
|
|
2917
3135
|
const preferredTable = truncateRows(table, TABLE_PREFERRED_MAX_ROWS);
|
|
2918
|
-
const preferredChars = countEmittedCellChars(preferredTable, ctx);
|
|
3136
|
+
const preferredChars = countEmittedCellChars(preferredTable, ctx, "table");
|
|
3137
|
+
const dataTableChars = countEmittedCellChars(table, ctx, "data_table");
|
|
2919
3138
|
return [
|
|
2920
3139
|
{
|
|
2921
3140
|
requires: ["dataTable"],
|
|
2922
|
-
budget: { blocks: 1, dataTableChars
|
|
3141
|
+
budget: { blocks: 1, dataTableChars },
|
|
2923
3142
|
emit: (emitCtx) => {
|
|
2924
3143
|
emitCtx.degradations.add({
|
|
2925
3144
|
nodeType: "table",
|
|
@@ -2945,11 +3164,11 @@ var TableAdapter = class {
|
|
|
2945
3164
|
return [buildTableBlock(preferredTable, emitCtx)];
|
|
2946
3165
|
}
|
|
2947
3166
|
},
|
|
2948
|
-
...markdownThenAscii(table,
|
|
3167
|
+
...markdownThenAscii(table, dataTableChars, "native table unavailable")
|
|
2949
3168
|
];
|
|
2950
3169
|
}
|
|
2951
3170
|
const dataChunk = truncateRows(table, DATA_TABLE_MAX_DATA_ROWS + 1);
|
|
2952
|
-
const dataChunkChars = countEmittedCellChars(dataChunk, ctx);
|
|
3171
|
+
const dataChunkChars = countEmittedCellChars(dataChunk, ctx, "data_table");
|
|
2953
3172
|
return [
|
|
2954
3173
|
{
|
|
2955
3174
|
requires: ["dataTable"],
|
|
@@ -2965,10 +3184,37 @@ var TableAdapter = class {
|
|
|
2965
3184
|
return [buildDataTable(dataChunk, emitCtx)];
|
|
2966
3185
|
}
|
|
2967
3186
|
},
|
|
2968
|
-
...markdownThenAscii(table,
|
|
3187
|
+
...markdownThenAscii(table, dataChunkChars, "oversized table")
|
|
2969
3188
|
];
|
|
2970
3189
|
}
|
|
2971
3190
|
};
|
|
3191
|
+
function normalizeTableRows(table, ctx) {
|
|
3192
|
+
const colCount = table.children[0]?.children.length ?? 0;
|
|
3193
|
+
if (colCount === 0 || table.children.every((row) => row.children.length === colCount)) {
|
|
3194
|
+
return table;
|
|
3195
|
+
}
|
|
3196
|
+
const children = table.children.map((row, rowIndex) => {
|
|
3197
|
+
if (row.children.length === colCount) {
|
|
3198
|
+
return row;
|
|
3199
|
+
}
|
|
3200
|
+
if (row.children.length > colCount) {
|
|
3201
|
+
ctx.degradations.add({
|
|
3202
|
+
nodeType: "table",
|
|
3203
|
+
from: `row[${String(rowIndex)}]: ${String(row.children.length)} cells`,
|
|
3204
|
+
to: `${String(colCount)} cells`,
|
|
3205
|
+
reason: "Cells beyond the header column count dropped (GFM ignores the excess)",
|
|
3206
|
+
path: ctx.path
|
|
3207
|
+
});
|
|
3208
|
+
return { ...row, children: row.children.slice(0, colCount) };
|
|
3209
|
+
}
|
|
3210
|
+
const padding = Array.from(
|
|
3211
|
+
{ length: colCount - row.children.length },
|
|
3212
|
+
() => ({ type: "tableCell", children: [] })
|
|
3213
|
+
);
|
|
3214
|
+
return { ...row, children: [...row.children, ...padding] };
|
|
3215
|
+
});
|
|
3216
|
+
return { ...table, children };
|
|
3217
|
+
}
|
|
2972
3218
|
function markdownThenAscii(table, cellChars, reason) {
|
|
2973
3219
|
const md = toPipeMarkdown(table);
|
|
2974
3220
|
return [
|
|
@@ -3014,7 +3260,7 @@ function buildTableBlock(table, ctx) {
|
|
|
3014
3260
|
}
|
|
3015
3261
|
function buildDataTable(table, ctx) {
|
|
3016
3262
|
const captionRaw = ctx.precedingHeading !== void 0 && ctx.precedingHeading.length > 0 ? ctx.precedingHeading : "Table";
|
|
3017
|
-
const caption = captionRaw
|
|
3263
|
+
const caption = sliceSurrogateSafe(captionRaw, 150);
|
|
3018
3264
|
if (captionRaw.length > 150) {
|
|
3019
3265
|
ctx.degradations.add({
|
|
3020
3266
|
nodeType: "table",
|
|
@@ -3025,12 +3271,7 @@ function buildDataTable(table, ctx) {
|
|
|
3025
3271
|
});
|
|
3026
3272
|
}
|
|
3027
3273
|
const rows = table.children.map(
|
|
3028
|
-
(row, rowIndex) => row.children.map((cell) =>
|
|
3029
|
-
if (rowIndex === 0) {
|
|
3030
|
-
return { type: "raw_text", text: toString5(cell) };
|
|
3031
|
-
}
|
|
3032
|
-
return cellToSlack(cell, ctx, false);
|
|
3033
|
-
})
|
|
3274
|
+
(row, rowIndex) => row.children.map((cell) => cellForDataTable(cell, ctx, rowIndex))
|
|
3034
3275
|
);
|
|
3035
3276
|
return {
|
|
3036
3277
|
type: "data_table",
|
|
@@ -3040,25 +3281,31 @@ function buildDataTable(table, ctx) {
|
|
|
3040
3281
|
row_header_column_index: 0
|
|
3041
3282
|
};
|
|
3042
3283
|
}
|
|
3284
|
+
function cellForDataTable(cell, ctx, rowIndex) {
|
|
3285
|
+
if (rowIndex === 0) {
|
|
3286
|
+
return { type: "raw_text", text: toString5(cell) };
|
|
3287
|
+
}
|
|
3288
|
+
return cellToSlack(cell, ctx, false);
|
|
3289
|
+
}
|
|
3043
3290
|
function cellToSlack(cell, ctx, header) {
|
|
3044
|
-
const
|
|
3045
|
-
const
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3291
|
+
const inline = emitPhrasing(cell.children, ctx);
|
|
3292
|
+
const single = inline.elements.length === 1 ? inline.elements[0] : void 0;
|
|
3293
|
+
const isEmpty = inline.elements.length === 0;
|
|
3294
|
+
const isSingleUnstyledText = single?.type === "text" && single.style === void 0;
|
|
3295
|
+
const plainText = isEmpty ? toString5(cell) : isSingleUnstyledText ? single.text : void 0;
|
|
3296
|
+
if (plainText !== void 0) {
|
|
3297
|
+
const trimmed = plainText.trim();
|
|
3298
|
+
if (!header && /^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
3299
|
+
return { type: "raw_number", value: Number(trimmed), text: trimmed };
|
|
3052
3300
|
}
|
|
3053
|
-
return { type: "raw_text", text:
|
|
3301
|
+
return { type: "raw_text", text: plainText };
|
|
3054
3302
|
}
|
|
3055
|
-
const inline = emitPhrasing(cell.children, ctx);
|
|
3056
3303
|
const rich = {
|
|
3057
3304
|
type: "rich_text",
|
|
3058
3305
|
elements: [
|
|
3059
3306
|
{
|
|
3060
3307
|
type: "rich_text_section",
|
|
3061
|
-
elements: inline.elements
|
|
3308
|
+
elements: inline.elements
|
|
3062
3309
|
}
|
|
3063
3310
|
]
|
|
3064
3311
|
};
|
|
@@ -3085,12 +3332,13 @@ function columnSettings(table) {
|
|
|
3085
3332
|
}
|
|
3086
3333
|
return settings;
|
|
3087
3334
|
}
|
|
3088
|
-
function countEmittedCellChars(table, ctx) {
|
|
3335
|
+
function countEmittedCellChars(table, ctx, mode) {
|
|
3089
3336
|
const probe = ctx.withDegradations(new RecordingDegradationCollector());
|
|
3090
3337
|
let n = 0;
|
|
3091
3338
|
for (const [rowIndex, row] of table.children.entries()) {
|
|
3092
3339
|
for (const cell of row.children) {
|
|
3093
|
-
|
|
3340
|
+
const emitted = mode === "data_table" ? cellForDataTable(cell, probe, rowIndex) : cellToSlack(cell, probe, rowIndex === 0);
|
|
3341
|
+
n += slackCellCharCount(emitted);
|
|
3094
3342
|
}
|
|
3095
3343
|
}
|
|
3096
3344
|
return n;
|
|
@@ -3213,27 +3461,50 @@ function emitUnicodeTaskTree(list, indent, ctx) {
|
|
|
3213
3461
|
});
|
|
3214
3462
|
effectiveIndent = RICH_TEXT_LIST_INDENT_MAX;
|
|
3215
3463
|
}
|
|
3216
|
-
const
|
|
3217
|
-
|
|
3464
|
+
const out = [];
|
|
3465
|
+
let pendingSections = [];
|
|
3466
|
+
const flush = () => {
|
|
3467
|
+
if (pendingSections.length === 0) {
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
out.push({
|
|
3471
|
+
type: "rich_text_list",
|
|
3472
|
+
style: "bullet",
|
|
3473
|
+
elements: pendingSections,
|
|
3474
|
+
indent: effectiveIndent
|
|
3475
|
+
});
|
|
3476
|
+
pendingSections = [];
|
|
3477
|
+
};
|
|
3218
3478
|
for (const item of list.children) {
|
|
3219
3479
|
const prefix = item.checked === true ? "\u2611 " : item.checked === false ? "\u2610 " : "";
|
|
3220
|
-
|
|
3480
|
+
pendingSections.push({
|
|
3221
3481
|
type: "rich_text_section",
|
|
3222
3482
|
elements: [{ type: "text", text: `${prefix}${itemText(item)}` }]
|
|
3223
3483
|
});
|
|
3484
|
+
const childLists = [];
|
|
3224
3485
|
for (const child of item.children) {
|
|
3225
3486
|
if (child.type === "list") {
|
|
3226
|
-
|
|
3487
|
+
childLists.push(child);
|
|
3227
3488
|
}
|
|
3228
3489
|
}
|
|
3490
|
+
if (childLists.length === 0) {
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
flush();
|
|
3494
|
+
for (const child of childLists) {
|
|
3495
|
+
out.push(...emitUnicodeTaskTree(child, effectiveIndent + 1, ctx));
|
|
3496
|
+
}
|
|
3229
3497
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3498
|
+
flush();
|
|
3499
|
+
if (out.length === 0) {
|
|
3500
|
+
out.push({
|
|
3501
|
+
type: "rich_text_list",
|
|
3502
|
+
style: "bullet",
|
|
3503
|
+
elements: [{ type: "rich_text_section", elements: [{ type: "text", text: " " }] }],
|
|
3504
|
+
indent: effectiveIndent
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
return out;
|
|
3237
3508
|
}
|
|
3238
3509
|
|
|
3239
3510
|
// src/adapters/thematic-break-adapter.ts
|
|
@@ -3308,117 +3579,37 @@ function validateBlocks(blocks) {
|
|
|
3308
3579
|
message: `Too many blocks: ${String(blocks.length)} > ${String(MAX_BLOCKS_PER_MESSAGE)}`
|
|
3309
3580
|
});
|
|
3310
3581
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3582
|
+
const counters = {
|
|
3583
|
+
markdownChars: 0,
|
|
3584
|
+
tableChars: 0,
|
|
3585
|
+
dataTableChars: 0,
|
|
3586
|
+
charts: 0
|
|
3587
|
+
};
|
|
3315
3588
|
blocks.forEach((block, i) => {
|
|
3316
|
-
|
|
3317
|
-
switch (block.type) {
|
|
3318
|
-
case "header":
|
|
3319
|
-
if (block.text.text.length > HEADER_TEXT_MAX_CHARS) {
|
|
3320
|
-
issues.push({ path, message: "header text > 150" });
|
|
3321
|
-
}
|
|
3322
|
-
if (block.level !== void 0 && (block.level < 1 || block.level > 4)) {
|
|
3323
|
-
issues.push({ path, message: "header level out of 1\u20134" });
|
|
3324
|
-
}
|
|
3325
|
-
break;
|
|
3326
|
-
case "section":
|
|
3327
|
-
if (block.text !== void 0 && block.text.text.length > SECTION_TEXT_MAX_CHARS) {
|
|
3328
|
-
issues.push({ path, message: "section text > 3000" });
|
|
3329
|
-
}
|
|
3330
|
-
break;
|
|
3331
|
-
case "image":
|
|
3332
|
-
if (block.alt_text.length > IMAGE_ALT_MAX_CHARS) {
|
|
3333
|
-
issues.push({ path, message: "image alt > 2000" });
|
|
3334
|
-
}
|
|
3335
|
-
if (block.image_url !== void 0 && block.image_url.length > IMAGE_URL_MAX_CHARS) {
|
|
3336
|
-
issues.push({ path, message: "image_url > 3000" });
|
|
3337
|
-
}
|
|
3338
|
-
break;
|
|
3339
|
-
case "context":
|
|
3340
|
-
if (block.elements.length > CONTEXT_ELEMENTS_MAX) {
|
|
3341
|
-
issues.push({ path, message: "context elements > 10" });
|
|
3342
|
-
}
|
|
3343
|
-
break;
|
|
3344
|
-
case "markdown":
|
|
3345
|
-
markdownChars += block.text.length;
|
|
3346
|
-
break;
|
|
3347
|
-
case "table": {
|
|
3348
|
-
if (block.rows.length > TABLE_MAX_ROWS) {
|
|
3349
|
-
issues.push({ path, message: "table rows > 100" });
|
|
3350
|
-
}
|
|
3351
|
-
for (const row of block.rows) {
|
|
3352
|
-
if (row.length > TABLE_MAX_COLS) {
|
|
3353
|
-
issues.push({ path, message: "table cols > 20" });
|
|
3354
|
-
}
|
|
3355
|
-
for (const cell of row) {
|
|
3356
|
-
tableChars += cellCharCount(cell);
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
break;
|
|
3360
|
-
}
|
|
3361
|
-
case "data_table": {
|
|
3362
|
-
if (block.rows.length > DATA_TABLE_MAX_DATA_ROWS + 1) {
|
|
3363
|
-
issues.push({ path, message: "data_table rows > 201" });
|
|
3364
|
-
}
|
|
3365
|
-
for (const row of block.rows) {
|
|
3366
|
-
if (row.length > TABLE_MAX_COLS) {
|
|
3367
|
-
issues.push({ path, message: "data_table cols > 20" });
|
|
3368
|
-
}
|
|
3369
|
-
for (const cell of row) {
|
|
3370
|
-
dataTableChars += cellCharCount(cell);
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
break;
|
|
3374
|
-
}
|
|
3375
|
-
case "data_visualization":
|
|
3376
|
-
charts += 1;
|
|
3377
|
-
if (block.title.length > CHART_TITLE_MAX_CHARS) {
|
|
3378
|
-
issues.push({ path, message: "chart title > 50" });
|
|
3379
|
-
}
|
|
3380
|
-
validateChart(block, path, issues);
|
|
3381
|
-
break;
|
|
3382
|
-
case "container":
|
|
3383
|
-
if (block.child_blocks.length > CONTAINER_CHILDREN_MAX) {
|
|
3384
|
-
issues.push({ path, message: "container children > 10" });
|
|
3385
|
-
}
|
|
3386
|
-
if (block.title !== void 0 && block.title.text.length > CONTAINER_TITLE_MAX_CHARS) {
|
|
3387
|
-
issues.push({ path, message: "container title > 150" });
|
|
3388
|
-
}
|
|
3389
|
-
break;
|
|
3390
|
-
case "divider":
|
|
3391
|
-
case "rich_text":
|
|
3392
|
-
break;
|
|
3393
|
-
default: {
|
|
3394
|
-
const exhaustive = block;
|
|
3395
|
-
void exhaustive;
|
|
3396
|
-
break;
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3589
|
+
validateBlock(block, `blocks[${String(i)}]`, issues, counters, 0);
|
|
3399
3590
|
});
|
|
3400
|
-
if (markdownChars > MARKDOWN_CUMULATIVE_MAX_CHARS) {
|
|
3591
|
+
if (counters.markdownChars > MARKDOWN_CUMULATIVE_MAX_CHARS) {
|
|
3401
3592
|
issues.push({
|
|
3402
3593
|
path: "$",
|
|
3403
|
-
message: `markdown cumulative ${String(markdownChars)} > 12000`
|
|
3594
|
+
message: `markdown cumulative ${String(counters.markdownChars)} > 12000`
|
|
3404
3595
|
});
|
|
3405
3596
|
}
|
|
3406
|
-
if (tableChars > TABLE_CELL_CHARS_MAX) {
|
|
3597
|
+
if (counters.tableChars > TABLE_CELL_CHARS_MAX) {
|
|
3407
3598
|
issues.push({
|
|
3408
3599
|
path: "$",
|
|
3409
|
-
message: `table chars ${String(tableChars)} > 10000`
|
|
3600
|
+
message: `table chars ${String(counters.tableChars)} > 10000`
|
|
3410
3601
|
});
|
|
3411
3602
|
}
|
|
3412
|
-
if (dataTableChars > DATA_TABLE_CELL_CHARS_MAX) {
|
|
3603
|
+
if (counters.dataTableChars > DATA_TABLE_CELL_CHARS_MAX) {
|
|
3413
3604
|
issues.push({
|
|
3414
3605
|
path: "$",
|
|
3415
|
-
message: `data_table chars ${String(dataTableChars)} > 20000`
|
|
3606
|
+
message: `data_table chars ${String(counters.dataTableChars)} > 20000`
|
|
3416
3607
|
});
|
|
3417
3608
|
}
|
|
3418
|
-
if (charts > DATA_VISUALIZATION_MAX_PER_MESSAGE) {
|
|
3609
|
+
if (counters.charts > DATA_VISUALIZATION_MAX_PER_MESSAGE) {
|
|
3419
3610
|
issues.push({
|
|
3420
3611
|
path: "$",
|
|
3421
|
-
message: `charts ${String(charts)} > ${String(DATA_VISUALIZATION_MAX_PER_MESSAGE)}`
|
|
3612
|
+
message: `charts ${String(counters.charts)} > ${String(DATA_VISUALIZATION_MAX_PER_MESSAGE)}`
|
|
3422
3613
|
});
|
|
3423
3614
|
}
|
|
3424
3615
|
if (issues.length > 0) {
|
|
@@ -3427,6 +3618,158 @@ function validateBlocks(blocks) {
|
|
|
3427
3618
|
}
|
|
3428
3619
|
return blocks;
|
|
3429
3620
|
}
|
|
3621
|
+
function validateBlock(block, path, issues, counters, depth) {
|
|
3622
|
+
if (depth > MAX_BLOCKS_PER_MESSAGE) {
|
|
3623
|
+
issues.push({ path, message: "container nesting too deep" });
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
switch (block.type) {
|
|
3627
|
+
case "header":
|
|
3628
|
+
if (block.text.text.length > HEADER_TEXT_MAX_CHARS) {
|
|
3629
|
+
issues.push({ path, message: "header text > 150" });
|
|
3630
|
+
}
|
|
3631
|
+
if (block.level !== void 0 && (block.level < 1 || block.level > 4)) {
|
|
3632
|
+
issues.push({ path, message: "header level out of 1\u20134" });
|
|
3633
|
+
}
|
|
3634
|
+
break;
|
|
3635
|
+
case "section":
|
|
3636
|
+
if (block.text !== void 0 && block.text.text.length > SECTION_TEXT_MAX_CHARS) {
|
|
3637
|
+
issues.push({ path, message: "section text > 3000" });
|
|
3638
|
+
}
|
|
3639
|
+
break;
|
|
3640
|
+
case "image":
|
|
3641
|
+
if (block.alt_text.length > IMAGE_ALT_MAX_CHARS) {
|
|
3642
|
+
issues.push({ path, message: "image alt > 2000" });
|
|
3643
|
+
}
|
|
3644
|
+
if (block.image_url !== void 0 && block.image_url.length > IMAGE_URL_MAX_CHARS) {
|
|
3645
|
+
issues.push({ path, message: "image_url > 3000" });
|
|
3646
|
+
}
|
|
3647
|
+
break;
|
|
3648
|
+
case "context":
|
|
3649
|
+
if (block.elements.length === 0) {
|
|
3650
|
+
issues.push({ path, message: "context elements empty" });
|
|
3651
|
+
} else if (block.elements.length > CONTEXT_ELEMENTS_MAX) {
|
|
3652
|
+
issues.push({ path, message: "context elements > 10" });
|
|
3653
|
+
}
|
|
3654
|
+
break;
|
|
3655
|
+
case "markdown":
|
|
3656
|
+
counters.markdownChars += block.text.length;
|
|
3657
|
+
break;
|
|
3658
|
+
case "table":
|
|
3659
|
+
if (block.rows.length > TABLE_MAX_ROWS) {
|
|
3660
|
+
issues.push({ path, message: "table rows > 100" });
|
|
3661
|
+
}
|
|
3662
|
+
counters.tableChars += validateTableCells(block.rows, path, "table cols > 20", issues);
|
|
3663
|
+
break;
|
|
3664
|
+
case "data_table":
|
|
3665
|
+
if (block.rows.length > DATA_TABLE_MAX_DATA_ROWS + 1) {
|
|
3666
|
+
issues.push({ path, message: "data_table rows > 201" });
|
|
3667
|
+
}
|
|
3668
|
+
counters.dataTableChars += validateTableCells(
|
|
3669
|
+
block.rows,
|
|
3670
|
+
path,
|
|
3671
|
+
"data_table cols > 20",
|
|
3672
|
+
issues
|
|
3673
|
+
);
|
|
3674
|
+
break;
|
|
3675
|
+
case "data_visualization":
|
|
3676
|
+
counters.charts += 1;
|
|
3677
|
+
if (block.title.length > CHART_TITLE_MAX_CHARS) {
|
|
3678
|
+
issues.push({ path, message: "chart title > 50" });
|
|
3679
|
+
}
|
|
3680
|
+
validateChart(block, path, issues);
|
|
3681
|
+
break;
|
|
3682
|
+
case "container":
|
|
3683
|
+
if (block.child_blocks.length > CONTAINER_CHILDREN_MAX) {
|
|
3684
|
+
issues.push({ path, message: "container children > 10" });
|
|
3685
|
+
}
|
|
3686
|
+
if (block.title !== void 0 && block.title.text.length > CONTAINER_TITLE_MAX_CHARS) {
|
|
3687
|
+
issues.push({ path, message: "container title > 150" });
|
|
3688
|
+
}
|
|
3689
|
+
if (block.rich_text_title !== void 0) {
|
|
3690
|
+
validateRichTextBlock(block.rich_text_title, `${path}.rich_text_title`, issues);
|
|
3691
|
+
}
|
|
3692
|
+
for (const [i, child] of block.child_blocks.entries()) {
|
|
3693
|
+
validateBlock(child, `${path}.child_blocks[${String(i)}]`, issues, counters, depth + 1);
|
|
3694
|
+
}
|
|
3695
|
+
break;
|
|
3696
|
+
case "divider":
|
|
3697
|
+
break;
|
|
3698
|
+
case "rich_text":
|
|
3699
|
+
validateRichTextBlock(block, path, issues);
|
|
3700
|
+
break;
|
|
3701
|
+
default: {
|
|
3702
|
+
const exhaustive = block;
|
|
3703
|
+
void exhaustive;
|
|
3704
|
+
break;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
function validateRichTextBlock(block, path, issues) {
|
|
3709
|
+
if (block.elements.length === 0) {
|
|
3710
|
+
issues.push({ path, message: "rich_text elements empty" });
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
for (const [i, element] of block.elements.entries()) {
|
|
3714
|
+
validateRichTextElement(element, `${path}.elements[${String(i)}]`, issues);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
function validateRichTextElement(element, path, issues) {
|
|
3718
|
+
switch (element.type) {
|
|
3719
|
+
case "rich_text_section":
|
|
3720
|
+
case "rich_text_quote":
|
|
3721
|
+
case "rich_text_preformatted":
|
|
3722
|
+
if (element.elements.length === 0) {
|
|
3723
|
+
issues.push({ path, message: `${element.type} elements empty` });
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
for (const [i, inline] of element.elements.entries()) {
|
|
3727
|
+
if (inline.type === "link" && !isValidRichTextLinkUrl(inline.url)) {
|
|
3728
|
+
issues.push({
|
|
3729
|
+
path: `${path}.elements[${String(i)}]`,
|
|
3730
|
+
message: "invalid rich text link URL"
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
break;
|
|
3735
|
+
case "rich_text_list":
|
|
3736
|
+
if (element.elements.length === 0) {
|
|
3737
|
+
issues.push({ path, message: "rich_text_list elements empty" });
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
for (const [i, section] of element.elements.entries()) {
|
|
3741
|
+
validateRichTextElement(section, `${path}.elements[${String(i)}]`, issues);
|
|
3742
|
+
}
|
|
3743
|
+
break;
|
|
3744
|
+
default: {
|
|
3745
|
+
const exhaustive = element;
|
|
3746
|
+
void exhaustive;
|
|
3747
|
+
break;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
function validateTableCells(rows, path, colsMessage, issues) {
|
|
3752
|
+
let chars = 0;
|
|
3753
|
+
for (const [r, row] of rows.entries()) {
|
|
3754
|
+
if (row.length > TABLE_MAX_COLS) {
|
|
3755
|
+
issues.push({ path, message: colsMessage });
|
|
3756
|
+
}
|
|
3757
|
+
for (const [c, cell] of row.entries()) {
|
|
3758
|
+
chars += cellCharCount(cell);
|
|
3759
|
+
if (cell.type === "rich_text") {
|
|
3760
|
+
validateRichTextBlock(cell, `${path}.rows[${String(r)}][${String(c)}]`, issues);
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
return chars;
|
|
3765
|
+
}
|
|
3766
|
+
var HTTP_OR_MAILTO_URL_RE = /^(https?:|mailto:)/i;
|
|
3767
|
+
function isValidRichTextLinkUrl(url) {
|
|
3768
|
+
if (url.length === 0 || url.length > IMAGE_URL_MAX_CHARS) {
|
|
3769
|
+
return false;
|
|
3770
|
+
}
|
|
3771
|
+
return HTTP_OR_MAILTO_URL_RE.test(url);
|
|
3772
|
+
}
|
|
3430
3773
|
function cellCharCount(cell) {
|
|
3431
3774
|
if (cell.type === "raw_text") {
|
|
3432
3775
|
return cell.text.length;
|