slackmark 0.1.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/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/adapters/blockquote-adapter.d.ts +11 -0
- package/dist/adapters/blockquote-adapter.d.ts.map +1 -0
- package/dist/adapters/code-fence-adapter.d.ts +12 -0
- package/dist/adapters/code-fence-adapter.d.ts.map +1 -0
- package/dist/adapters/heading-adapter.d.ts +11 -0
- package/dist/adapters/heading-adapter.d.ts.map +1 -0
- package/dist/adapters/html-block-adapter.d.ts +11 -0
- package/dist/adapters/html-block-adapter.d.ts.map +1 -0
- package/dist/adapters/inline.d.ts +41 -0
- package/dist/adapters/inline.d.ts.map +1 -0
- package/dist/adapters/list-adapter.d.ts +12 -0
- package/dist/adapters/list-adapter.d.ts.map +1 -0
- package/dist/adapters/math-block-adapter.d.ts +11 -0
- package/dist/adapters/math-block-adapter.d.ts.map +1 -0
- package/dist/adapters/mermaid-fence-adapter.d.ts +19 -0
- package/dist/adapters/mermaid-fence-adapter.d.ts.map +1 -0
- package/dist/adapters/node-adapter.d.ts +21 -0
- package/dist/adapters/node-adapter.d.ts.map +1 -0
- package/dist/adapters/paragraph-adapter.d.ts +11 -0
- package/dist/adapters/paragraph-adapter.d.ts.map +1 -0
- package/dist/adapters/skip-adapter.d.ts +11 -0
- package/dist/adapters/skip-adapter.d.ts.map +1 -0
- package/dist/adapters/table-adapter.d.ts +11 -0
- package/dist/adapters/table-adapter.d.ts.map +1 -0
- package/dist/adapters/task-list-adapter.d.ts +11 -0
- package/dist/adapters/task-list-adapter.d.ts.map +1 -0
- package/dist/adapters/thematic-break-adapter.d.ts +11 -0
- package/dist/adapters/thematic-break-adapter.d.ts.map +1 -0
- package/dist/assemble/message-assembler.d.ts +24 -0
- package/dist/assemble/message-assembler.d.ts.map +1 -0
- package/dist/assemble/text-fallback.d.ts +10 -0
- package/dist/assemble/text-fallback.d.ts.map +1 -0
- package/dist/assert-never.d.ts +5 -0
- package/dist/assert-never.d.ts.map +1 -0
- package/dist/blocks/types.d.ts +196 -0
- package/dist/blocks/types.d.ts.map +1 -0
- package/dist/budget/conversion-context.d.ts +44 -0
- package/dist/budget/conversion-context.d.ts.map +1 -0
- package/dist/budget/recording-degradation-collector.d.ts +11 -0
- package/dist/budget/recording-degradation-collector.d.ts.map +1 -0
- package/dist/budget/standard-budget-ledger.d.ts +35 -0
- package/dist/budget/standard-budget-ledger.d.ts.map +1 -0
- package/dist/capabilities/capabilities.d.ts +42 -0
- package/dist/capabilities/capabilities.d.ts.map +1 -0
- package/dist/constants.d.ts +59 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/converter/create-converter.d.ts +25 -0
- package/dist/converter/create-converter.d.ts.map +1 -0
- package/dist/converter/resolve-config.d.ts +7 -0
- package/dist/converter/resolve-config.d.ts.map +1 -0
- package/dist/converter/slackmark-converter.d.ts +33 -0
- package/dist/converter/slackmark-converter.d.ts.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3402 -0
- package/dist/index.js.map +1 -0
- package/dist/mermaid/mermaid-pie-parser.d.ts +10 -0
- package/dist/mermaid/mermaid-pie-parser.d.ts.map +1 -0
- package/dist/mermaid/mermaid-xychart-parser.d.ts +10 -0
- package/dist/mermaid/mermaid-xychart-parser.d.ts.map +1 -0
- package/dist/parsers/remark-markdown-parser.d.ts +9 -0
- package/dist/parsers/remark-markdown-parser.d.ts.map +1 -0
- package/dist/registry/adapter-registry.d.ts +13 -0
- package/dist/registry/adapter-registry.d.ts.map +1 -0
- package/dist/renderers/codecogs-renderer.d.ts +18 -0
- package/dist/renderers/codecogs-renderer.d.ts.map +1 -0
- package/dist/renderers/encoding.d.ts +5 -0
- package/dist/renderers/encoding.d.ts.map +1 -0
- package/dist/renderers/kroki-renderer.d.ts +27 -0
- package/dist/renderers/kroki-renderer.d.ts.map +1 -0
- package/dist/renderers/mermaid-ink-renderer.d.ts +24 -0
- package/dist/renderers/mermaid-ink-renderer.d.ts.map +1 -0
- package/dist/renderers/quickchart-renderer.d.ts +25 -0
- package/dist/renderers/quickchart-renderer.d.ts.map +1 -0
- package/dist/renderers/url-utils.d.ts +3 -0
- package/dist/renderers/url-utils.d.ts.map +1 -0
- package/dist/renderers.d.ts +12 -0
- package/dist/renderers.d.ts.map +1 -0
- package/dist/renderers.js +269 -0
- package/dist/renderers.js.map +1 -0
- package/dist/slack/fetch-slack-uploader.d.ts +52 -0
- package/dist/slack/fetch-slack-uploader.d.ts.map +1 -0
- package/dist/slack/slack-upload-error.d.ts +25 -0
- package/dist/slack/slack-upload-error.d.ts.map +1 -0
- package/dist/slack.d.ts +8 -0
- package/dist/slack.d.ts.map +1 -0
- package/dist/slack.js +255 -0
- package/dist/slack.js.map +1 -0
- package/dist/types.d.ts +202 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validate/validate-blocks.d.ts +18 -0
- package/dist/validate/validate-blocks.d.ts.map +1 -0
- package/package.json +76 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3402 @@
|
|
|
1
|
+
// src/capabilities/capabilities.ts
|
|
2
|
+
var CapabilityPresetName = {
|
|
3
|
+
Latest: "latest",
|
|
4
|
+
Conservative: "conservative",
|
|
5
|
+
D2025_02: "2025-02",
|
|
6
|
+
D2025_08: "2025-08",
|
|
7
|
+
D2026_03: "2026-03",
|
|
8
|
+
D2026_05: "2026-05",
|
|
9
|
+
D2026_06: "2026-06"
|
|
10
|
+
};
|
|
11
|
+
var ALL_OFF = {
|
|
12
|
+
richText: false,
|
|
13
|
+
markdownBlock: false,
|
|
14
|
+
tableBlock: false,
|
|
15
|
+
preformattedLanguage: false,
|
|
16
|
+
headerLevel: false,
|
|
17
|
+
dataTable: false,
|
|
18
|
+
dataVisualization: false,
|
|
19
|
+
containerBlock: false
|
|
20
|
+
};
|
|
21
|
+
var CONSERVATIVE = {
|
|
22
|
+
...ALL_OFF,
|
|
23
|
+
richText: true
|
|
24
|
+
};
|
|
25
|
+
var P_2025_02 = {
|
|
26
|
+
...CONSERVATIVE,
|
|
27
|
+
markdownBlock: true
|
|
28
|
+
};
|
|
29
|
+
var P_2025_08 = {
|
|
30
|
+
...P_2025_02,
|
|
31
|
+
tableBlock: true
|
|
32
|
+
};
|
|
33
|
+
var P_2026_03 = {
|
|
34
|
+
...P_2025_08,
|
|
35
|
+
preformattedLanguage: true,
|
|
36
|
+
headerLevel: true
|
|
37
|
+
};
|
|
38
|
+
var P_2026_05 = {
|
|
39
|
+
...P_2026_03,
|
|
40
|
+
dataTable: true
|
|
41
|
+
};
|
|
42
|
+
var P_2026_06 = {
|
|
43
|
+
...P_2026_05,
|
|
44
|
+
dataVisualization: true,
|
|
45
|
+
containerBlock: true
|
|
46
|
+
};
|
|
47
|
+
var LATEST = { ...P_2026_06 };
|
|
48
|
+
var CAPABILITY_PRESETS = {
|
|
49
|
+
latest: LATEST,
|
|
50
|
+
conservative: CONSERVATIVE,
|
|
51
|
+
"2025-02": P_2025_02,
|
|
52
|
+
"2025-08": P_2025_08,
|
|
53
|
+
"2026-03": P_2026_03,
|
|
54
|
+
"2026-05": P_2026_05,
|
|
55
|
+
"2026-06": P_2026_06
|
|
56
|
+
};
|
|
57
|
+
function resolveCapabilities(input, overrides) {
|
|
58
|
+
let base;
|
|
59
|
+
if (input === void 0) {
|
|
60
|
+
base = CAPABILITY_PRESETS.latest;
|
|
61
|
+
} else if (typeof input === "string") {
|
|
62
|
+
const preset = CAPABILITY_PRESETS[input];
|
|
63
|
+
if (preset === void 0) {
|
|
64
|
+
base = CAPABILITY_PRESETS.latest;
|
|
65
|
+
} else {
|
|
66
|
+
base = preset;
|
|
67
|
+
}
|
|
68
|
+
} else if (isFullFlags(input)) {
|
|
69
|
+
base = input;
|
|
70
|
+
} else {
|
|
71
|
+
base = { ...CAPABILITY_PRESETS.latest, ...input };
|
|
72
|
+
}
|
|
73
|
+
if (overrides === void 0) {
|
|
74
|
+
return Object.freeze({ ...base });
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze({ ...base, ...overrides });
|
|
77
|
+
}
|
|
78
|
+
function isFullFlags(value) {
|
|
79
|
+
return typeof value.richText === "boolean" && 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
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/constants.ts
|
|
83
|
+
var MAX_BLOCKS_PER_MESSAGE = 50;
|
|
84
|
+
var SECTION_TEXT_MAX_CHARS = 3e3;
|
|
85
|
+
var HEADER_TEXT_MAX_CHARS = 150;
|
|
86
|
+
var HEADER_LEVEL_MAX = 4;
|
|
87
|
+
var IMAGE_URL_MAX_CHARS = 3e3;
|
|
88
|
+
var IMAGE_ALT_MAX_CHARS = 2e3;
|
|
89
|
+
var CONTEXT_ELEMENTS_MAX = 10;
|
|
90
|
+
var MARKDOWN_CUMULATIVE_MAX_CHARS = 12e3;
|
|
91
|
+
var TABLE_MAX_ROWS = 100;
|
|
92
|
+
var TABLE_MAX_COLS = 20;
|
|
93
|
+
var TABLE_CELL_CHARS_MAX = 1e4;
|
|
94
|
+
var TABLE_PREFERRED_MAX_ROWS = 50;
|
|
95
|
+
var DATA_TABLE_MAX_DATA_ROWS = 200;
|
|
96
|
+
var DATA_TABLE_CELL_CHARS_MAX = 2e4;
|
|
97
|
+
var CHART_TITLE_MAX_CHARS = 50;
|
|
98
|
+
var CHART_LABEL_MAX_CHARS = 20;
|
|
99
|
+
var CHART_SEGMENTS_MAX = 12;
|
|
100
|
+
var CHART_SERIES_MAX = 12;
|
|
101
|
+
var CHART_POINTS_MAX = 20;
|
|
102
|
+
var DATA_VISUALIZATION_MAX_PER_MESSAGE = 2;
|
|
103
|
+
var MESSAGE_TEXT_SAFETY_MAX_CHARS = 4e4;
|
|
104
|
+
var RICH_TEXT_LIST_INDENT_MAX = 6;
|
|
105
|
+
var CONTAINER_CHILDREN_MAX = 10;
|
|
106
|
+
var CONTAINER_TITLE_MAX_CHARS = 150;
|
|
107
|
+
var TABLE_CELL_WRAP_THRESHOLD = 40;
|
|
108
|
+
var ELLIPSIS = "\u2026";
|
|
109
|
+
|
|
110
|
+
// src/budget/standard-budget-ledger.ts
|
|
111
|
+
var StandardBudgetLedger = class {
|
|
112
|
+
maxBlocks;
|
|
113
|
+
enforceChartCap;
|
|
114
|
+
blocks;
|
|
115
|
+
tableChars;
|
|
116
|
+
dataTableChars;
|
|
117
|
+
markdownChars;
|
|
118
|
+
charts;
|
|
119
|
+
totalChars;
|
|
120
|
+
constructor(maxBlocks = MAX_BLOCKS_PER_MESSAGE, options) {
|
|
121
|
+
this.maxBlocks = maxBlocks;
|
|
122
|
+
this.enforceChartCap = options?.enforceChartCap ?? true;
|
|
123
|
+
this.blocks = 0;
|
|
124
|
+
this.tableChars = 0;
|
|
125
|
+
this.dataTableChars = 0;
|
|
126
|
+
this.markdownChars = 0;
|
|
127
|
+
this.charts = 0;
|
|
128
|
+
this.totalChars = 0;
|
|
129
|
+
}
|
|
130
|
+
tryReserveBlocks(count) {
|
|
131
|
+
if (this.blocks + count > this.maxBlocks) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
this.blocks += count;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
tryReserveTableChars(chars) {
|
|
138
|
+
if (this.tableChars + chars > TABLE_CELL_CHARS_MAX) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
this.tableChars += chars;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
tryReserveDataTableChars(chars) {
|
|
145
|
+
if (this.dataTableChars + chars > DATA_TABLE_CELL_CHARS_MAX) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
this.dataTableChars += chars;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
tryReserveMarkdownChars(chars) {
|
|
152
|
+
if (this.markdownChars + chars > MARKDOWN_CUMULATIVE_MAX_CHARS) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
this.markdownChars += chars;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
tryReserveChart() {
|
|
159
|
+
if (this.enforceChartCap && this.charts + 1 > DATA_VISUALIZATION_MAX_PER_MESSAGE) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
this.charts += 1;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
tryReserveTotalChars(chars) {
|
|
166
|
+
if (this.totalChars + chars > MESSAGE_TEXT_SAFETY_MAX_CHARS) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
this.totalChars += chars;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
tryReserveClaim(claim) {
|
|
173
|
+
if (claim === void 0) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const nextBlocks = this.blocks + (claim.blocks ?? 0);
|
|
177
|
+
const nextTable = this.tableChars + (claim.tableChars ?? 0);
|
|
178
|
+
const nextData = this.dataTableChars + (claim.dataTableChars ?? 0);
|
|
179
|
+
const nextMd = this.markdownChars + (claim.markdownChars ?? 0);
|
|
180
|
+
const nextCharts = this.charts + (claim.chart === true ? 1 : 0);
|
|
181
|
+
const nextTotal = this.totalChars + (claim.totalChars ?? 0);
|
|
182
|
+
if (nextBlocks > this.maxBlocks) return false;
|
|
183
|
+
if (nextTable > TABLE_CELL_CHARS_MAX) return false;
|
|
184
|
+
if (nextData > DATA_TABLE_CELL_CHARS_MAX) return false;
|
|
185
|
+
if (nextMd > MARKDOWN_CUMULATIVE_MAX_CHARS) return false;
|
|
186
|
+
if (this.enforceChartCap && nextCharts > DATA_VISUALIZATION_MAX_PER_MESSAGE) return false;
|
|
187
|
+
if (nextTotal > MESSAGE_TEXT_SAFETY_MAX_CHARS) return false;
|
|
188
|
+
this.blocks = nextBlocks;
|
|
189
|
+
this.tableChars = nextTable;
|
|
190
|
+
this.dataTableChars = nextData;
|
|
191
|
+
this.markdownChars = nextMd;
|
|
192
|
+
this.charts = nextCharts;
|
|
193
|
+
this.totalChars = nextTotal;
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
remainingBlocks() {
|
|
197
|
+
return this.maxBlocks - this.blocks;
|
|
198
|
+
}
|
|
199
|
+
snapshot() {
|
|
200
|
+
return {
|
|
201
|
+
blocks: this.blocks,
|
|
202
|
+
tableChars: this.tableChars,
|
|
203
|
+
dataTableChars: this.dataTableChars,
|
|
204
|
+
markdownChars: this.markdownChars,
|
|
205
|
+
charts: this.charts,
|
|
206
|
+
totalChars: this.totalChars
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
restore(snapshot) {
|
|
210
|
+
this.blocks = snapshot.blocks;
|
|
211
|
+
this.tableChars = snapshot.tableChars;
|
|
212
|
+
this.dataTableChars = snapshot.dataTableChars;
|
|
213
|
+
this.markdownChars = snapshot.markdownChars;
|
|
214
|
+
this.charts = snapshot.charts;
|
|
215
|
+
this.totalChars = snapshot.totalChars;
|
|
216
|
+
}
|
|
217
|
+
resetForNewMessage() {
|
|
218
|
+
this.blocks = 0;
|
|
219
|
+
this.tableChars = 0;
|
|
220
|
+
this.dataTableChars = 0;
|
|
221
|
+
this.markdownChars = 0;
|
|
222
|
+
this.charts = 0;
|
|
223
|
+
this.totalChars = 0;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/budget/recording-degradation-collector.ts
|
|
228
|
+
var RecordingDegradationCollector = class {
|
|
229
|
+
items;
|
|
230
|
+
constructor() {
|
|
231
|
+
this.items = [];
|
|
232
|
+
}
|
|
233
|
+
add(d) {
|
|
234
|
+
this.items.push(d);
|
|
235
|
+
}
|
|
236
|
+
all() {
|
|
237
|
+
return this.items.slice();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/budget/conversion-context.ts
|
|
242
|
+
var ConversionContext = class _ConversionContext {
|
|
243
|
+
config;
|
|
244
|
+
budget;
|
|
245
|
+
degradations;
|
|
246
|
+
definitions;
|
|
247
|
+
footnotes;
|
|
248
|
+
footnoteOrder;
|
|
249
|
+
precedingHeading;
|
|
250
|
+
path;
|
|
251
|
+
constructor(deps) {
|
|
252
|
+
this.config = deps.config;
|
|
253
|
+
this.budget = deps.budget;
|
|
254
|
+
this.degradations = deps.degradations;
|
|
255
|
+
this.definitions = deps.definitions;
|
|
256
|
+
this.footnotes = deps.footnotes;
|
|
257
|
+
this.footnoteOrder = deps.footnoteOrder;
|
|
258
|
+
this.precedingHeading = deps.precedingHeading;
|
|
259
|
+
this.path = deps.path;
|
|
260
|
+
}
|
|
261
|
+
withPath(segment) {
|
|
262
|
+
return new _ConversionContext({
|
|
263
|
+
config: this.config,
|
|
264
|
+
budget: this.budget,
|
|
265
|
+
degradations: this.degradations,
|
|
266
|
+
definitions: this.definitions,
|
|
267
|
+
footnotes: this.footnotes,
|
|
268
|
+
footnoteOrder: this.footnoteOrder,
|
|
269
|
+
precedingHeading: this.precedingHeading,
|
|
270
|
+
path: this.path.length === 0 ? segment : `${this.path}/${segment}`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
withPrecedingHeading(heading) {
|
|
274
|
+
return new _ConversionContext({
|
|
275
|
+
config: this.config,
|
|
276
|
+
budget: this.budget,
|
|
277
|
+
degradations: this.degradations,
|
|
278
|
+
definitions: this.definitions,
|
|
279
|
+
footnotes: this.footnotes,
|
|
280
|
+
footnoteOrder: this.footnoteOrder,
|
|
281
|
+
precedingHeading: heading,
|
|
282
|
+
path: this.path
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
withDegradations(degradations) {
|
|
286
|
+
return new _ConversionContext({
|
|
287
|
+
config: this.config,
|
|
288
|
+
budget: this.budget,
|
|
289
|
+
degradations,
|
|
290
|
+
definitions: this.definitions,
|
|
291
|
+
footnotes: this.footnotes,
|
|
292
|
+
footnoteOrder: this.footnoteOrder,
|
|
293
|
+
precedingHeading: this.precedingHeading,
|
|
294
|
+
path: this.path
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/registry/adapter-registry.ts
|
|
300
|
+
var AdapterRegistry = class {
|
|
301
|
+
adapters;
|
|
302
|
+
constructor(adapters) {
|
|
303
|
+
this.adapters = adapters;
|
|
304
|
+
}
|
|
305
|
+
async emit(node, ctx) {
|
|
306
|
+
for (const adapter of this.adapters) {
|
|
307
|
+
if (!adapter.match(node, ctx)) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const candidates = adapter.plan(node, ctx);
|
|
311
|
+
let budgetBlocked = false;
|
|
312
|
+
let emittedEmpty = false;
|
|
313
|
+
for (const candidate of candidates) {
|
|
314
|
+
if (!capabilitiesAllow(candidate.requires, ctx)) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const beforeClaim = ctx.budget.snapshot();
|
|
318
|
+
if (!ctx.budget.tryReserveClaim(candidate.budget)) {
|
|
319
|
+
budgetBlocked = true;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const blocks = await candidate.emit(ctx);
|
|
323
|
+
if (blocks.length === 0) {
|
|
324
|
+
ctx.budget.restore(beforeClaim);
|
|
325
|
+
emittedEmpty = true;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
return blocks;
|
|
329
|
+
}
|
|
330
|
+
if (emittedEmpty && !budgetBlocked) {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
ctx.degradations.add({
|
|
334
|
+
nodeType: node.type,
|
|
335
|
+
from: "adapter",
|
|
336
|
+
to: "empty",
|
|
337
|
+
reason: budgetBlocked ? "Message budget exhausted (blocks/chars/charts); content omitted" : "All candidates failed capability/budget gates",
|
|
338
|
+
path: ctx.path
|
|
339
|
+
});
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
ctx.degradations.add({
|
|
343
|
+
nodeType: node.type,
|
|
344
|
+
from: node.type,
|
|
345
|
+
to: "skip",
|
|
346
|
+
reason: "No adapter matched node",
|
|
347
|
+
path: ctx.path
|
|
348
|
+
});
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
function capabilitiesAllow(requires, ctx) {
|
|
353
|
+
if (requires === void 0) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
for (const flag of requires) {
|
|
357
|
+
if (!ctx.config.capabilities[flag]) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/assert-never.ts
|
|
365
|
+
function assertNever(value, message) {
|
|
366
|
+
const detail = message ?? "Unexpected value";
|
|
367
|
+
throw new Error(`${detail}: ${JSON.stringify(value)}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/types.ts
|
|
371
|
+
var OverflowMode = {
|
|
372
|
+
Degrade: "degrade",
|
|
373
|
+
Split: "split"
|
|
374
|
+
};
|
|
375
|
+
var ConversionStrategy = {
|
|
376
|
+
RichTextFirst: "rich-text-first",
|
|
377
|
+
MarkdownBlock: "markdown-block"
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/assemble/message-assembler.ts
|
|
381
|
+
var MessageAssembler = class {
|
|
382
|
+
plainText;
|
|
383
|
+
constructor(deps) {
|
|
384
|
+
this.plainText = deps.plainText;
|
|
385
|
+
}
|
|
386
|
+
assemble(blocks, ctx, footnoteBlocks) {
|
|
387
|
+
const coalesced = coalesceRichText(blocks);
|
|
388
|
+
if (ctx.config.overflow === OverflowMode.Split) {
|
|
389
|
+
return this.splitMessages([...coalesced, ...footnoteBlocks], ctx);
|
|
390
|
+
}
|
|
391
|
+
const noteCount = footnoteBlocks.length;
|
|
392
|
+
const bodyCap = Math.max(0, MAX_BLOCKS_PER_MESSAGE - noteCount);
|
|
393
|
+
const clippedBody = coalesced.slice(0, bodyCap);
|
|
394
|
+
if (coalesced.length > bodyCap) {
|
|
395
|
+
ctx.degradations.add({
|
|
396
|
+
nodeType: "message",
|
|
397
|
+
from: `${String(coalesced.length)} body blocks`,
|
|
398
|
+
to: `${String(bodyCap)} body blocks`,
|
|
399
|
+
reason: noteCount > 0 ? `overflow=degrade truncated body to reserve ${String(noteCount)} footnote blocks` : "overflow=degrade truncated to 50 blocks",
|
|
400
|
+
path: ctx.path
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
let notes = footnoteBlocks;
|
|
404
|
+
if (notes.length > MAX_BLOCKS_PER_MESSAGE) {
|
|
405
|
+
notes = footnoteBlocks.slice(0, MAX_BLOCKS_PER_MESSAGE);
|
|
406
|
+
ctx.degradations.add({
|
|
407
|
+
nodeType: "footnote",
|
|
408
|
+
from: `${String(footnoteBlocks.length)} footnote blocks`,
|
|
409
|
+
to: `${String(MAX_BLOCKS_PER_MESSAGE)} footnote blocks`,
|
|
410
|
+
reason: "Footnote blocks alone exceed 50; truncated",
|
|
411
|
+
path: ctx.path
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const finalBlocks = [...clippedBody, ...notes];
|
|
415
|
+
return [this.toMessage(finalBlocks, ctx)];
|
|
416
|
+
}
|
|
417
|
+
splitMessages(blocks, ctx) {
|
|
418
|
+
const messages = [];
|
|
419
|
+
let current = [];
|
|
420
|
+
let ledger = new StandardBudgetLedger(MAX_BLOCKS_PER_MESSAGE);
|
|
421
|
+
const flush = () => {
|
|
422
|
+
if (current.length === 0) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
messages.push(this.toMessage(current, ctx));
|
|
426
|
+
current = [];
|
|
427
|
+
ledger = new StandardBudgetLedger(MAX_BLOCKS_PER_MESSAGE);
|
|
428
|
+
};
|
|
429
|
+
for (const block of blocks) {
|
|
430
|
+
const claim = claimForBlock(block);
|
|
431
|
+
if (!ledger.tryReserveClaim(claim)) {
|
|
432
|
+
flush();
|
|
433
|
+
if (!ledger.tryReserveClaim(claim)) {
|
|
434
|
+
ctx.degradations.add({
|
|
435
|
+
nodeType: "message",
|
|
436
|
+
from: block.type,
|
|
437
|
+
to: "omitted",
|
|
438
|
+
reason: "Block exceeds per-message budget; omitted",
|
|
439
|
+
path: ctx.path
|
|
440
|
+
});
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
current.push(block);
|
|
445
|
+
}
|
|
446
|
+
flush();
|
|
447
|
+
if (messages.length > 1) {
|
|
448
|
+
ctx.degradations.add({
|
|
449
|
+
nodeType: "message",
|
|
450
|
+
from: "single",
|
|
451
|
+
to: `${String(messages.length)} messages`,
|
|
452
|
+
reason: "Split at semantic block boundaries (per-message budgets)",
|
|
453
|
+
path: ctx.path
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return messages.length > 0 ? messages : [{ blocks: [], text: " " }];
|
|
457
|
+
}
|
|
458
|
+
toMessage(blocks, ctx) {
|
|
459
|
+
const raw = this.plainText.renderFromBlocks(blocks);
|
|
460
|
+
if (raw.length <= MESSAGE_TEXT_SAFETY_MAX_CHARS) {
|
|
461
|
+
return { blocks, text: raw };
|
|
462
|
+
}
|
|
463
|
+
ctx.degradations.add({
|
|
464
|
+
nodeType: "message",
|
|
465
|
+
from: `${String(raw.length)} text chars`,
|
|
466
|
+
to: `${String(MESSAGE_TEXT_SAFETY_MAX_CHARS)} text chars`,
|
|
467
|
+
reason: "Notification text truncated to 40k safety limit",
|
|
468
|
+
path: ctx.path
|
|
469
|
+
});
|
|
470
|
+
return { blocks, text: raw.slice(0, MESSAGE_TEXT_SAFETY_MAX_CHARS) };
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
function buildFootnoteBlocks(entries, degradations) {
|
|
474
|
+
if (entries.length === 0) {
|
|
475
|
+
return [];
|
|
476
|
+
}
|
|
477
|
+
const blocks = [{ type: "divider" }];
|
|
478
|
+
const elements = [];
|
|
479
|
+
for (const e of entries) {
|
|
480
|
+
const note = `[${String(e.n)}] ${e.text}`;
|
|
481
|
+
if (note.length > SECTION_TEXT_MAX_CHARS) {
|
|
482
|
+
degradations?.add({
|
|
483
|
+
nodeType: "footnote",
|
|
484
|
+
from: `${String(note.length)} chars`,
|
|
485
|
+
to: `${String(SECTION_TEXT_MAX_CHARS)} chars`,
|
|
486
|
+
reason: "Footnote context text truncated to 3000 chars",
|
|
487
|
+
path: "footnote"
|
|
488
|
+
});
|
|
489
|
+
elements.push({ type: "mrkdwn", text: note.slice(0, SECTION_TEXT_MAX_CHARS) });
|
|
490
|
+
} else {
|
|
491
|
+
elements.push({ type: "mrkdwn", text: note });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (let i = 0; i < elements.length; i += CONTEXT_ELEMENTS_MAX) {
|
|
495
|
+
const chunk = elements.slice(i, i + CONTEXT_ELEMENTS_MAX);
|
|
496
|
+
blocks.push({ type: "context", elements: chunk });
|
|
497
|
+
}
|
|
498
|
+
return blocks;
|
|
499
|
+
}
|
|
500
|
+
function claimForBlock(block) {
|
|
501
|
+
switch (block.type) {
|
|
502
|
+
case "data_visualization":
|
|
503
|
+
return { blocks: 1, chart: true };
|
|
504
|
+
case "markdown":
|
|
505
|
+
return { blocks: 1, markdownChars: block.text.length };
|
|
506
|
+
case "table":
|
|
507
|
+
return { blocks: 1, tableChars: countEmittedTableChars(block) };
|
|
508
|
+
case "data_table":
|
|
509
|
+
return { blocks: 1, dataTableChars: countEmittedTableChars(block) };
|
|
510
|
+
case "rich_text":
|
|
511
|
+
case "header":
|
|
512
|
+
case "divider":
|
|
513
|
+
case "section":
|
|
514
|
+
case "image":
|
|
515
|
+
case "context":
|
|
516
|
+
case "container":
|
|
517
|
+
return { blocks: 1 };
|
|
518
|
+
default:
|
|
519
|
+
return assertNever(block);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function countEmittedTableChars(block) {
|
|
523
|
+
let total = 0;
|
|
524
|
+
for (const row of block.rows) {
|
|
525
|
+
for (const cell of row) {
|
|
526
|
+
total += cellTextLength(cell);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return total;
|
|
530
|
+
}
|
|
531
|
+
function cellTextLength(cell) {
|
|
532
|
+
if (cell.type === "raw_text" || cell.type === "raw_number") {
|
|
533
|
+
return cell.text.length;
|
|
534
|
+
}
|
|
535
|
+
return JSON.stringify(cell).length;
|
|
536
|
+
}
|
|
537
|
+
function coalesceRichText(blocks) {
|
|
538
|
+
const out = [];
|
|
539
|
+
let pending;
|
|
540
|
+
const flushPending = () => {
|
|
541
|
+
if (pending === void 0 || pending.length === 0) {
|
|
542
|
+
pending = void 0;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const block = { type: "rich_text", elements: pending };
|
|
546
|
+
out.push(block);
|
|
547
|
+
pending = void 0;
|
|
548
|
+
};
|
|
549
|
+
for (const block of blocks) {
|
|
550
|
+
if (block.type === "rich_text" && isCoalescable(block)) {
|
|
551
|
+
if (pending === void 0) {
|
|
552
|
+
pending = [...block.elements];
|
|
553
|
+
} else {
|
|
554
|
+
pending.push(...block.elements);
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
flushPending();
|
|
558
|
+
out.push(block);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
flushPending();
|
|
562
|
+
return out;
|
|
563
|
+
}
|
|
564
|
+
function isCoalescable(block) {
|
|
565
|
+
return block.elements.every((e) => e.type === "rich_text_section");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/assemble/text-fallback.ts
|
|
569
|
+
var DeterministicPlainTextRenderer = class {
|
|
570
|
+
render(markdown) {
|
|
571
|
+
const parts = [];
|
|
572
|
+
let i = 0;
|
|
573
|
+
const src = markdown;
|
|
574
|
+
while (i < src.length) {
|
|
575
|
+
if (src.startsWith("```", i)) {
|
|
576
|
+
const end = src.indexOf("```", i + 3);
|
|
577
|
+
if (end === -1) {
|
|
578
|
+
parts.push(src.slice(i + 3));
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
parts.push(src.slice(i + 3, end).trim());
|
|
582
|
+
i = end + 3;
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
parts.push(src[i] ?? "");
|
|
586
|
+
i += 1;
|
|
587
|
+
}
|
|
588
|
+
const raw = parts.join("").replace(/!\[[^\]]{0,200}\]\([^)]{0,3000}\)/g, " ").replace(/\[([^\]]{0,200})\]\([^)]{0,3000}\)/g, "$1").replace(/[#>*_`~]/g, "").replace(/\s+/g, " ").trim();
|
|
589
|
+
return raw.length > 0 ? raw : " ";
|
|
590
|
+
}
|
|
591
|
+
renderFromBlocks(blocks) {
|
|
592
|
+
const parts = [];
|
|
593
|
+
for (const block of blocks) {
|
|
594
|
+
switch (block.type) {
|
|
595
|
+
case "header":
|
|
596
|
+
parts.push(block.text.text);
|
|
597
|
+
break;
|
|
598
|
+
case "section":
|
|
599
|
+
if (block.text !== void 0) {
|
|
600
|
+
parts.push(stripMrkdwn(block.text.text));
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
case "rich_text":
|
|
604
|
+
parts.push(richTextToPlain(block.elements));
|
|
605
|
+
break;
|
|
606
|
+
case "markdown":
|
|
607
|
+
parts.push(this.render(block.text));
|
|
608
|
+
break;
|
|
609
|
+
case "divider":
|
|
610
|
+
parts.push("---");
|
|
611
|
+
break;
|
|
612
|
+
case "image":
|
|
613
|
+
parts.push(block.alt_text);
|
|
614
|
+
break;
|
|
615
|
+
case "context":
|
|
616
|
+
for (const el of block.elements) {
|
|
617
|
+
if (el.type === "mrkdwn" || el.type === "plain_text") {
|
|
618
|
+
parts.push(stripMrkdwn(el.text));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
break;
|
|
622
|
+
case "table":
|
|
623
|
+
case "data_table":
|
|
624
|
+
parts.push(tableToPlain(block.rows));
|
|
625
|
+
break;
|
|
626
|
+
case "data_visualization":
|
|
627
|
+
parts.push(block.title);
|
|
628
|
+
break;
|
|
629
|
+
case "container":
|
|
630
|
+
if (block.title !== void 0) {
|
|
631
|
+
parts.push(block.title.text);
|
|
632
|
+
}
|
|
633
|
+
parts.push(this.renderFromBlocks(block.child_blocks));
|
|
634
|
+
break;
|
|
635
|
+
default:
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const text = parts.map((p) => p.trim()).filter((p) => p.length > 0).join("\n").trim();
|
|
640
|
+
return text.length > 0 ? text : " ";
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
function stripMrkdwn(text) {
|
|
644
|
+
return text.replace(/<([^|>]{1,3000})\|([^>]{0,500})>/g, "$2").replace(/<([^>]{1,3000})>/g, "$1").replace(/[*_~`]/g, "");
|
|
645
|
+
}
|
|
646
|
+
function richTextToPlain(elements) {
|
|
647
|
+
const parts = [];
|
|
648
|
+
for (const el of elements) {
|
|
649
|
+
switch (el.type) {
|
|
650
|
+
case "rich_text_section":
|
|
651
|
+
case "rich_text_quote":
|
|
652
|
+
parts.push(inlinesToPlain(el.elements));
|
|
653
|
+
break;
|
|
654
|
+
case "rich_text_list":
|
|
655
|
+
for (const item of el.elements) {
|
|
656
|
+
parts.push(`\u2022 ${inlinesToPlain(item.elements)}`);
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
case "rich_text_preformatted":
|
|
660
|
+
parts.push(inlinesToPlain(el.elements));
|
|
661
|
+
break;
|
|
662
|
+
default:
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return parts.join("\n");
|
|
667
|
+
}
|
|
668
|
+
function inlinesToPlain(elements) {
|
|
669
|
+
const parts = [];
|
|
670
|
+
for (const el of elements) {
|
|
671
|
+
switch (el.type) {
|
|
672
|
+
case "text":
|
|
673
|
+
parts.push(el.text);
|
|
674
|
+
break;
|
|
675
|
+
case "link":
|
|
676
|
+
parts.push(el.text ?? el.url);
|
|
677
|
+
break;
|
|
678
|
+
case "emoji":
|
|
679
|
+
parts.push(`:${el.name}:`);
|
|
680
|
+
break;
|
|
681
|
+
case "user":
|
|
682
|
+
parts.push(`@${el.user_id}`);
|
|
683
|
+
break;
|
|
684
|
+
case "channel":
|
|
685
|
+
parts.push(`#${el.channel_id}`);
|
|
686
|
+
break;
|
|
687
|
+
default:
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return parts.join("");
|
|
692
|
+
}
|
|
693
|
+
function tableToPlain(rows) {
|
|
694
|
+
const lines = [];
|
|
695
|
+
for (const row of rows) {
|
|
696
|
+
const cells = row.map((cell) => cellPlain(cell));
|
|
697
|
+
lines.push(cells.join(" | "));
|
|
698
|
+
}
|
|
699
|
+
return lines.join("\n");
|
|
700
|
+
}
|
|
701
|
+
function cellPlain(cell) {
|
|
702
|
+
if (typeof cell !== "object" || cell === null) {
|
|
703
|
+
return "";
|
|
704
|
+
}
|
|
705
|
+
const c = cell;
|
|
706
|
+
if (c.type === "raw_text" && typeof c.text === "string") {
|
|
707
|
+
return c.text;
|
|
708
|
+
}
|
|
709
|
+
if (c.type === "raw_number") {
|
|
710
|
+
return typeof c.text === "string" ? c.text : String(c.value ?? "");
|
|
711
|
+
}
|
|
712
|
+
if (c.type === "rich_text" && Array.isArray(c.elements)) {
|
|
713
|
+
return richTextToPlain(c.elements);
|
|
714
|
+
}
|
|
715
|
+
return "";
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/parsers/remark-markdown-parser.ts
|
|
719
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
720
|
+
import remarkGfm from "remark-gfm";
|
|
721
|
+
import remarkMath from "remark-math";
|
|
722
|
+
import remarkParse from "remark-parse";
|
|
723
|
+
import { unified } from "unified";
|
|
724
|
+
var RemarkMarkdownParser = class {
|
|
725
|
+
async parse(markdown, options) {
|
|
726
|
+
const processor = unified().use(remarkParse).use(remarkGfm);
|
|
727
|
+
if (options.enableFrontmatter) {
|
|
728
|
+
processor.use(remarkFrontmatter, ["yaml", "toml"]);
|
|
729
|
+
}
|
|
730
|
+
if (options.enableMath) {
|
|
731
|
+
processor.use(remarkMath);
|
|
732
|
+
}
|
|
733
|
+
return processor.parse(markdown);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// src/mermaid/mermaid-pie-parser.ts
|
|
738
|
+
var COMMENT_RE = /%%[^\n]{0,500}/g;
|
|
739
|
+
var TITLE_RE = /^\s*pie\b(?:\s+showData)?(?:\s+title\s+(.+))?\s*$/i;
|
|
740
|
+
var SHOW_DATA_RE = /\bshowData\b/i;
|
|
741
|
+
var ENTRY_RE = /^\s*"([^"]{1,200})"\s*:\s*(-?\d+(?:\.\d+)?)\s*$/;
|
|
742
|
+
var MermaidPieParser = class {
|
|
743
|
+
diagramType;
|
|
744
|
+
constructor() {
|
|
745
|
+
this.diagramType = "pie";
|
|
746
|
+
}
|
|
747
|
+
parse(source) {
|
|
748
|
+
const cleaned = source.replace(COMMENT_RE, "");
|
|
749
|
+
const lines = cleaned.split(/\r?\n/);
|
|
750
|
+
let title;
|
|
751
|
+
let showData = false;
|
|
752
|
+
const segments = [];
|
|
753
|
+
let seenHeader = false;
|
|
754
|
+
for (const raw of lines) {
|
|
755
|
+
const line = raw.trim();
|
|
756
|
+
if (line.length === 0) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (!seenHeader) {
|
|
760
|
+
if (!/^\s*pie\b/i.test(line)) {
|
|
761
|
+
return { ok: false, reason: "Not a pie diagram" };
|
|
762
|
+
}
|
|
763
|
+
seenHeader = true;
|
|
764
|
+
showData = SHOW_DATA_RE.test(line);
|
|
765
|
+
const tm = TITLE_RE.exec(line);
|
|
766
|
+
const titlePart = tm?.[1]?.trim();
|
|
767
|
+
if (titlePart !== void 0 && titlePart.length > 0) {
|
|
768
|
+
title = titlePart;
|
|
769
|
+
}
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const titleLine = /^\s*title\s+(.+)\s*$/i.exec(line);
|
|
773
|
+
if (titleLine !== null && title === void 0) {
|
|
774
|
+
title = (titleLine[1] ?? "").trim();
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const em = ENTRY_RE.exec(line);
|
|
778
|
+
if (em === null) {
|
|
779
|
+
return { ok: false, reason: `Invalid pie entry: ${line}` };
|
|
780
|
+
}
|
|
781
|
+
const label = em[1] ?? "";
|
|
782
|
+
const value = Number(em[2]);
|
|
783
|
+
if (!(value > 0)) {
|
|
784
|
+
return { ok: false, reason: `Pie values must be > 0 (got ${String(value)})` };
|
|
785
|
+
}
|
|
786
|
+
segments.push({ label, value });
|
|
787
|
+
}
|
|
788
|
+
if (!seenHeader) {
|
|
789
|
+
return { ok: false, reason: "Missing pie header" };
|
|
790
|
+
}
|
|
791
|
+
if (segments.length === 0) {
|
|
792
|
+
return { ok: false, reason: "Pie has no segments" };
|
|
793
|
+
}
|
|
794
|
+
const ast = {
|
|
795
|
+
kind: "pie",
|
|
796
|
+
title,
|
|
797
|
+
showData,
|
|
798
|
+
segments
|
|
799
|
+
};
|
|
800
|
+
return { ok: true, value: ast };
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// src/mermaid/mermaid-xychart-parser.ts
|
|
805
|
+
var COMMENT_RE2 = /%%[^\n]{0,500}/g;
|
|
806
|
+
var NUM = String.raw`-?\d+(?:\.\d+)?`;
|
|
807
|
+
var NUM_LIST_RE = new RegExp(String.raw`\[\s*((?:${NUM}\s*,\s*)*${NUM})\s*\]`);
|
|
808
|
+
var STR_LIST_RE = /\[\s*((?:"[^"]{0,200}"\s*,\s*)*"[^"]{0,200}")\s*\]/;
|
|
809
|
+
var MermaidXyChartParser = class {
|
|
810
|
+
diagramType;
|
|
811
|
+
constructor() {
|
|
812
|
+
this.diagramType = "xychart-beta";
|
|
813
|
+
}
|
|
814
|
+
parse(source) {
|
|
815
|
+
const cleaned = source.replace(COMMENT_RE2, "");
|
|
816
|
+
const lines = cleaned.split(/\r?\n/);
|
|
817
|
+
let title;
|
|
818
|
+
let categories = [];
|
|
819
|
+
let xLabel;
|
|
820
|
+
let yLabel;
|
|
821
|
+
let yMin;
|
|
822
|
+
let yMax;
|
|
823
|
+
const bars = [];
|
|
824
|
+
const linesSeries = [];
|
|
825
|
+
let seenHeader = false;
|
|
826
|
+
for (const raw of lines) {
|
|
827
|
+
const line = raw.trim();
|
|
828
|
+
if (line.length === 0) {
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (!seenHeader) {
|
|
832
|
+
if (!/^\s*xychart-beta\b/i.test(line)) {
|
|
833
|
+
return { ok: false, reason: "Not an xychart-beta diagram" };
|
|
834
|
+
}
|
|
835
|
+
seenHeader = true;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const titleM = /^\s*title\s+"([^"]{0,200})"\s*$/i.exec(line) ?? /^\s*title\s+(.+)\s*$/i.exec(line);
|
|
839
|
+
if (titleM !== null) {
|
|
840
|
+
title = (titleM[1] ?? "").trim();
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (/^\s*x-axis\b/i.test(line)) {
|
|
844
|
+
const strList = STR_LIST_RE.exec(line);
|
|
845
|
+
if (strList !== null) {
|
|
846
|
+
categories = parseStringList(strList[1] ?? "");
|
|
847
|
+
const labelM = /^\s*x-axis\s+"([^"]{0,200})"\s+/i.exec(line);
|
|
848
|
+
if (labelM !== null) {
|
|
849
|
+
xLabel = labelM[1];
|
|
850
|
+
}
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
const rangeM = new RegExp(
|
|
854
|
+
String.raw`x-axis\s+(?:"([^"]{0,200})"\s+)?(${NUM})\s*-->\s*(${NUM})`,
|
|
855
|
+
"i"
|
|
856
|
+
).exec(line);
|
|
857
|
+
if (rangeM !== null) {
|
|
858
|
+
xLabel = rangeM[1];
|
|
859
|
+
const min = Number(rangeM[2]);
|
|
860
|
+
const max = Number(rangeM[3]);
|
|
861
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) {
|
|
862
|
+
return { ok: false, reason: `Invalid x-axis range: ${line}` };
|
|
863
|
+
}
|
|
864
|
+
const span = max - min + 1;
|
|
865
|
+
if (span > CHART_POINTS_MAX) {
|
|
866
|
+
return {
|
|
867
|
+
ok: false,
|
|
868
|
+
reason: `x-axis range has ${String(span)} points (max ${String(CHART_POINTS_MAX)})`
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
categories = [];
|
|
872
|
+
for (let i = min; i <= max; i += 1) {
|
|
873
|
+
categories.push(String(i));
|
|
874
|
+
}
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
return { ok: false, reason: `Invalid x-axis: ${line}` };
|
|
878
|
+
}
|
|
879
|
+
if (/^\s*y-axis\b/i.test(line)) {
|
|
880
|
+
const ym = new RegExp(
|
|
881
|
+
String.raw`y-axis\s+(?:"([^"]{0,200})"\s+)?(?:(${NUM})\s*-->\s*(${NUM}))?`,
|
|
882
|
+
"i"
|
|
883
|
+
).exec(line);
|
|
884
|
+
if (ym !== null) {
|
|
885
|
+
yLabel = ym[1];
|
|
886
|
+
if (ym[2] !== void 0 && ym[3] !== void 0) {
|
|
887
|
+
yMin = Number(ym[2]);
|
|
888
|
+
yMax = Number(ym[3]);
|
|
889
|
+
}
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
return { ok: false, reason: `Invalid y-axis: ${line}` };
|
|
893
|
+
}
|
|
894
|
+
if (/^\s*bar\b/i.test(line)) {
|
|
895
|
+
const nums = NUM_LIST_RE.exec(line);
|
|
896
|
+
if (nums === null) {
|
|
897
|
+
return { ok: false, reason: `Invalid bar series: ${line}` };
|
|
898
|
+
}
|
|
899
|
+
bars.push(parseNumberList(nums[1] ?? ""));
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (/^\s*line\b/i.test(line)) {
|
|
903
|
+
const nums = NUM_LIST_RE.exec(line);
|
|
904
|
+
if (nums === null) {
|
|
905
|
+
return { ok: false, reason: `Invalid line series: ${line}` };
|
|
906
|
+
}
|
|
907
|
+
linesSeries.push(parseNumberList(nums[1] ?? ""));
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
return { ok: false, reason: `Unrecognized xychart line: ${line}` };
|
|
911
|
+
}
|
|
912
|
+
if (!seenHeader) {
|
|
913
|
+
return { ok: false, reason: "Missing xychart-beta header" };
|
|
914
|
+
}
|
|
915
|
+
if (categories.length === 0) {
|
|
916
|
+
return { ok: false, reason: "xychart missing categories" };
|
|
917
|
+
}
|
|
918
|
+
if (bars.length === 0 && linesSeries.length === 0) {
|
|
919
|
+
return { ok: false, reason: "xychart has no series" };
|
|
920
|
+
}
|
|
921
|
+
const ast = {
|
|
922
|
+
kind: "xychart-beta",
|
|
923
|
+
title,
|
|
924
|
+
categories,
|
|
925
|
+
xLabel,
|
|
926
|
+
yLabel,
|
|
927
|
+
yMin,
|
|
928
|
+
yMax,
|
|
929
|
+
bars,
|
|
930
|
+
lines: linesSeries
|
|
931
|
+
};
|
|
932
|
+
return { ok: true, value: ast };
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
function parseStringList(inner) {
|
|
936
|
+
const out = [];
|
|
937
|
+
const re = /"([^"]{0,200})"/g;
|
|
938
|
+
let m = re.exec(inner);
|
|
939
|
+
while (m !== null) {
|
|
940
|
+
out.push(m[1] ?? "");
|
|
941
|
+
m = re.exec(inner);
|
|
942
|
+
}
|
|
943
|
+
return out;
|
|
944
|
+
}
|
|
945
|
+
function parseNumberList(inner) {
|
|
946
|
+
return inner.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => Number(s));
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/converter/slackmark-converter.ts
|
|
950
|
+
import { toString } from "mdast-util-to-string";
|
|
951
|
+
|
|
952
|
+
// src/errors.ts
|
|
953
|
+
var ConfigurationError = class extends Error {
|
|
954
|
+
name;
|
|
955
|
+
constructor(message, options) {
|
|
956
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
957
|
+
this.name = "ConfigurationError";
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
// src/converter/resolve-config.ts
|
|
962
|
+
function resolveConfig(defaults, overrides) {
|
|
963
|
+
const merged = {
|
|
964
|
+
...defaults,
|
|
965
|
+
...overrides,
|
|
966
|
+
capabilityOverrides: {
|
|
967
|
+
...defaults.capabilityOverrides,
|
|
968
|
+
...overrides?.capabilityOverrides
|
|
969
|
+
},
|
|
970
|
+
renderers: overrides?.renderers ?? defaults.renderers,
|
|
971
|
+
uploader: overrides?.uploader ?? defaults.uploader,
|
|
972
|
+
mentionResolvers: overrides?.mentionResolvers ?? defaults.mentionResolvers
|
|
973
|
+
};
|
|
974
|
+
const capabilities = resolveCapabilities(merged.capabilities, merged.capabilityOverrides);
|
|
975
|
+
const overflow = merged.overflow ?? OverflowMode.Degrade;
|
|
976
|
+
if (overflow !== OverflowMode.Degrade && overflow !== OverflowMode.Split) {
|
|
977
|
+
throw new ConfigurationError(
|
|
978
|
+
`Invalid overflow mode ${JSON.stringify(overflow)}; use "degrade" or "split".`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
const strategy = merged.strategy ?? ConversionStrategy.RichTextFirst;
|
|
982
|
+
if (strategy !== ConversionStrategy.RichTextFirst && strategy !== ConversionStrategy.MarkdownBlock) {
|
|
983
|
+
throw new ConfigurationError(
|
|
984
|
+
`Invalid strategy ${JSON.stringify(strategy)}; use "rich-text-first" or "markdown-block".`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
const renderers = merged.renderers ?? [];
|
|
988
|
+
return Object.freeze({
|
|
989
|
+
capabilities,
|
|
990
|
+
overflow,
|
|
991
|
+
strategy,
|
|
992
|
+
renderers,
|
|
993
|
+
uploader: merged.uploader,
|
|
994
|
+
enableMath: merged.enableMath === true,
|
|
995
|
+
enableFrontmatter: merged.enableFrontmatter === true,
|
|
996
|
+
mentionResolvers: merged.mentionResolvers
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// src/converter/slackmark-converter.ts
|
|
1001
|
+
var SlackMarkConverter = class {
|
|
1002
|
+
parser;
|
|
1003
|
+
registry;
|
|
1004
|
+
assembler;
|
|
1005
|
+
plainText;
|
|
1006
|
+
defaultOptions;
|
|
1007
|
+
constructor(deps) {
|
|
1008
|
+
this.parser = deps.parser;
|
|
1009
|
+
this.registry = deps.registry;
|
|
1010
|
+
this.assembler = deps.assembler;
|
|
1011
|
+
this.plainText = deps.plainText;
|
|
1012
|
+
this.defaultOptions = deps.defaultOptions;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Convert markdown to a single message. Always uses overflow=degrade so content
|
|
1016
|
+
* is truncated with degradations rather than silently dropping split messages.
|
|
1017
|
+
* Callers that need multi-message output should use {@link convertToMessages}.
|
|
1018
|
+
*/
|
|
1019
|
+
async convert(markdown, options) {
|
|
1020
|
+
const multi = await this.convertToMessages(markdown, {
|
|
1021
|
+
...options,
|
|
1022
|
+
overflow: OverflowMode.Degrade
|
|
1023
|
+
});
|
|
1024
|
+
const first = multi.messages[0] ?? { blocks: [], text: " " };
|
|
1025
|
+
return {
|
|
1026
|
+
blocks: first.blocks,
|
|
1027
|
+
text: first.text,
|
|
1028
|
+
degradations: multi.degradations
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Convert markdown into one or more messages at semantic boundaries.
|
|
1033
|
+
*/
|
|
1034
|
+
async convertToMessages(markdown, options) {
|
|
1035
|
+
const config = resolveConfig(this.defaultOptions, options);
|
|
1036
|
+
const degradations = new RecordingDegradationCollector();
|
|
1037
|
+
const splitting = config.overflow === OverflowMode.Split;
|
|
1038
|
+
const budget = new StandardBudgetLedger(
|
|
1039
|
+
splitting ? Number.MAX_SAFE_INTEGER : MAX_BLOCKS_PER_MESSAGE,
|
|
1040
|
+
{ enforceChartCap: !splitting }
|
|
1041
|
+
);
|
|
1042
|
+
try {
|
|
1043
|
+
if (config.strategy === ConversionStrategy.MarkdownBlock) {
|
|
1044
|
+
return this.emitAsMarkdownBlocks(markdown, config, degradations, budget);
|
|
1045
|
+
}
|
|
1046
|
+
const tree = await this.parser.parse(markdown, {
|
|
1047
|
+
enableMath: config.enableMath,
|
|
1048
|
+
enableFrontmatter: config.enableFrontmatter
|
|
1049
|
+
});
|
|
1050
|
+
const { definitions, footnotes, footnoteOrder } = collectRefs(tree);
|
|
1051
|
+
let precedingHeading;
|
|
1052
|
+
const emitted = [];
|
|
1053
|
+
let index = 0;
|
|
1054
|
+
for (const node of tree.children) {
|
|
1055
|
+
if (splitting) {
|
|
1056
|
+
budget.resetForNewMessage();
|
|
1057
|
+
}
|
|
1058
|
+
const ctx = new ConversionContext({
|
|
1059
|
+
config,
|
|
1060
|
+
budget,
|
|
1061
|
+
degradations,
|
|
1062
|
+
definitions,
|
|
1063
|
+
footnotes,
|
|
1064
|
+
footnoteOrder,
|
|
1065
|
+
precedingHeading,
|
|
1066
|
+
path: `root[${String(index)}]`
|
|
1067
|
+
});
|
|
1068
|
+
if (node.type === "heading") {
|
|
1069
|
+
precedingHeading = toString(node);
|
|
1070
|
+
}
|
|
1071
|
+
const blocks = await this.registry.emit(node, ctx);
|
|
1072
|
+
emitted.push(...blocks);
|
|
1073
|
+
index += 1;
|
|
1074
|
+
}
|
|
1075
|
+
const footnoteEntries = footnoteOrder.map((id, i) => {
|
|
1076
|
+
const def = footnotes.get(id);
|
|
1077
|
+
return {
|
|
1078
|
+
n: i + 1,
|
|
1079
|
+
text: def !== void 0 ? toString(def).trim() : id
|
|
1080
|
+
};
|
|
1081
|
+
});
|
|
1082
|
+
const footnoteBlocks = buildFootnoteBlocks(footnoteEntries, degradations);
|
|
1083
|
+
const assembleCtx = new ConversionContext({
|
|
1084
|
+
config,
|
|
1085
|
+
budget,
|
|
1086
|
+
degradations,
|
|
1087
|
+
definitions,
|
|
1088
|
+
footnotes,
|
|
1089
|
+
footnoteOrder,
|
|
1090
|
+
precedingHeading,
|
|
1091
|
+
path: "assemble"
|
|
1092
|
+
});
|
|
1093
|
+
const messages = this.assembler.assemble(emitted, assembleCtx, footnoteBlocks);
|
|
1094
|
+
return {
|
|
1095
|
+
messages,
|
|
1096
|
+
degradations: degradations.all()
|
|
1097
|
+
};
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
degradations.add({
|
|
1100
|
+
nodeType: "document",
|
|
1101
|
+
from: "convert",
|
|
1102
|
+
to: "section",
|
|
1103
|
+
reason: err instanceof Error ? err.message : "Unknown conversion error",
|
|
1104
|
+
path: "root"
|
|
1105
|
+
});
|
|
1106
|
+
const text = safeNotificationText(this.plainText, markdown, degradations);
|
|
1107
|
+
return {
|
|
1108
|
+
messages: [
|
|
1109
|
+
{
|
|
1110
|
+
blocks: [
|
|
1111
|
+
{
|
|
1112
|
+
type: "section",
|
|
1113
|
+
text: { type: "mrkdwn", text: clampSectionFallback(text, degradations) }
|
|
1114
|
+
}
|
|
1115
|
+
],
|
|
1116
|
+
text
|
|
1117
|
+
}
|
|
1118
|
+
],
|
|
1119
|
+
degradations: degradations.all()
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
emitAsMarkdownBlocks(markdown, config, degradations, budget) {
|
|
1124
|
+
const chunks = chunkMarkdown(markdown, 12e3);
|
|
1125
|
+
let blocks = [];
|
|
1126
|
+
if (!config.capabilities.markdownBlock) {
|
|
1127
|
+
degradations.add({
|
|
1128
|
+
nodeType: "document",
|
|
1129
|
+
from: "markdown-block",
|
|
1130
|
+
to: "section",
|
|
1131
|
+
reason: "markdownBlock capability off; emitting section fallback",
|
|
1132
|
+
path: "root"
|
|
1133
|
+
});
|
|
1134
|
+
const text = safeNotificationText(this.plainText, markdown, degradations);
|
|
1135
|
+
blocks = [
|
|
1136
|
+
{
|
|
1137
|
+
type: "section",
|
|
1138
|
+
text: { type: "mrkdwn", text: clampSectionFallback(text, degradations) }
|
|
1139
|
+
}
|
|
1140
|
+
];
|
|
1141
|
+
} else {
|
|
1142
|
+
for (const chunk of chunks) {
|
|
1143
|
+
if (config.overflow === OverflowMode.Split) {
|
|
1144
|
+
budget.resetForNewMessage();
|
|
1145
|
+
}
|
|
1146
|
+
if (!budget.tryReserveClaim({ blocks: 1, markdownChars: chunk.length })) {
|
|
1147
|
+
degradations.add({
|
|
1148
|
+
nodeType: "document",
|
|
1149
|
+
from: "markdown-block",
|
|
1150
|
+
to: "truncated",
|
|
1151
|
+
reason: "markdown 12k cumulative budget exhausted",
|
|
1152
|
+
path: "root"
|
|
1153
|
+
});
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
blocks.push({ type: "markdown", text: chunk });
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
const assembleCtx = new ConversionContext({
|
|
1160
|
+
config,
|
|
1161
|
+
budget,
|
|
1162
|
+
degradations,
|
|
1163
|
+
definitions: /* @__PURE__ */ new Map(),
|
|
1164
|
+
footnotes: /* @__PURE__ */ new Map(),
|
|
1165
|
+
footnoteOrder: [],
|
|
1166
|
+
precedingHeading: void 0,
|
|
1167
|
+
path: "assemble"
|
|
1168
|
+
});
|
|
1169
|
+
const messages = this.assembler.assemble(blocks, assembleCtx, []);
|
|
1170
|
+
return {
|
|
1171
|
+
messages,
|
|
1172
|
+
degradations: degradations.all()
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
function safeNotificationText(plainText, markdown, degradations) {
|
|
1177
|
+
const raw = plainText.render(markdown);
|
|
1178
|
+
if (raw.length <= MESSAGE_TEXT_SAFETY_MAX_CHARS) {
|
|
1179
|
+
return raw;
|
|
1180
|
+
}
|
|
1181
|
+
degradations.add({
|
|
1182
|
+
nodeType: "message",
|
|
1183
|
+
from: `${String(raw.length)} text chars`,
|
|
1184
|
+
to: `${String(MESSAGE_TEXT_SAFETY_MAX_CHARS)} text chars`,
|
|
1185
|
+
reason: "Notification text truncated to 40k safety limit",
|
|
1186
|
+
path: "root"
|
|
1187
|
+
});
|
|
1188
|
+
return raw.slice(0, MESSAGE_TEXT_SAFETY_MAX_CHARS);
|
|
1189
|
+
}
|
|
1190
|
+
function clampSectionFallback(text, degradations) {
|
|
1191
|
+
if (text.length <= SECTION_TEXT_MAX_CHARS) {
|
|
1192
|
+
return text;
|
|
1193
|
+
}
|
|
1194
|
+
degradations.add({
|
|
1195
|
+
nodeType: "section",
|
|
1196
|
+
from: `${String(text.length)} chars`,
|
|
1197
|
+
to: `${String(SECTION_TEXT_MAX_CHARS)} chars`,
|
|
1198
|
+
reason: "Section text truncated to 3000 chars",
|
|
1199
|
+
path: "root"
|
|
1200
|
+
});
|
|
1201
|
+
return text.slice(0, SECTION_TEXT_MAX_CHARS);
|
|
1202
|
+
}
|
|
1203
|
+
function collectRefs(tree) {
|
|
1204
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
1205
|
+
const footnotes = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const node of tree.children) {
|
|
1207
|
+
if (node.type === "definition") {
|
|
1208
|
+
definitions.set(node.identifier.toLowerCase(), node);
|
|
1209
|
+
}
|
|
1210
|
+
if (node.type === "footnoteDefinition") {
|
|
1211
|
+
footnotes.set(node.identifier, node);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const footnoteOrder = [];
|
|
1215
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1216
|
+
walkFootnoteReferences(tree, (id) => {
|
|
1217
|
+
if (!seen.has(id)) {
|
|
1218
|
+
seen.add(id);
|
|
1219
|
+
footnoteOrder.push(id);
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
return { definitions, footnotes, footnoteOrder };
|
|
1223
|
+
}
|
|
1224
|
+
function walkFootnoteReferences(node, onRef) {
|
|
1225
|
+
if (node === null || typeof node !== "object") {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const record = node;
|
|
1229
|
+
if (record.type === "footnoteDefinition") {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (record.type === "footnoteReference" && typeof record.identifier === "string") {
|
|
1233
|
+
onRef(record.identifier);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
if (record.children !== void 0) {
|
|
1237
|
+
for (const child of record.children) {
|
|
1238
|
+
walkFootnoteReferences(child, onRef);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function chunkMarkdown(text, max) {
|
|
1243
|
+
if (text.length <= max) {
|
|
1244
|
+
return [text.length > 0 ? text : " "];
|
|
1245
|
+
}
|
|
1246
|
+
const out = [];
|
|
1247
|
+
for (let i = 0; i < text.length; i += max) {
|
|
1248
|
+
out.push(text.slice(i, i + max));
|
|
1249
|
+
}
|
|
1250
|
+
return out;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/adapters/blockquote-adapter.ts
|
|
1254
|
+
import { toString as toString3 } from "mdast-util-to-string";
|
|
1255
|
+
|
|
1256
|
+
// src/adapters/inline.ts
|
|
1257
|
+
import { toString as toString2 } from "mdast-util-to-string";
|
|
1258
|
+
var EMOJI_SHORTCODE_RE = /:([a-z0-9_+-]{1,64}):/g;
|
|
1259
|
+
var KNOWN_SLACK_EMOJI = /* @__PURE__ */ new Set([
|
|
1260
|
+
"smile",
|
|
1261
|
+
"grinning",
|
|
1262
|
+
"wink",
|
|
1263
|
+
"blush",
|
|
1264
|
+
"wave",
|
|
1265
|
+
"thumbsup",
|
|
1266
|
+
"thumbsdown",
|
|
1267
|
+
"tada",
|
|
1268
|
+
"rocket",
|
|
1269
|
+
"fire",
|
|
1270
|
+
"heart",
|
|
1271
|
+
"eyes",
|
|
1272
|
+
"thinking_face",
|
|
1273
|
+
"sweat_smile",
|
|
1274
|
+
"joy",
|
|
1275
|
+
"sob",
|
|
1276
|
+
"clap",
|
|
1277
|
+
"100",
|
|
1278
|
+
"white_check_mark",
|
|
1279
|
+
"x",
|
|
1280
|
+
"warning",
|
|
1281
|
+
"bulb",
|
|
1282
|
+
"memo",
|
|
1283
|
+
"mag",
|
|
1284
|
+
"link",
|
|
1285
|
+
"gear",
|
|
1286
|
+
"bug",
|
|
1287
|
+
"sparkles",
|
|
1288
|
+
"+1",
|
|
1289
|
+
"-1",
|
|
1290
|
+
"ok_hand",
|
|
1291
|
+
"pray",
|
|
1292
|
+
"star",
|
|
1293
|
+
"zap",
|
|
1294
|
+
"check",
|
|
1295
|
+
"heavy_check_mark"
|
|
1296
|
+
]);
|
|
1297
|
+
var HTTP_URL_RE = /^(https?:|mailto:)/i;
|
|
1298
|
+
var IMAGE_EXT_RE = /\.(png|jpe?g|gif)(?:\?|#|$)/i;
|
|
1299
|
+
function emitPhrasing(nodes, ctx, baseStyle = {}) {
|
|
1300
|
+
const elements = [];
|
|
1301
|
+
const hoistedImages = [];
|
|
1302
|
+
let pendingAnchor;
|
|
1303
|
+
const flushPendingAnchor = () => {
|
|
1304
|
+
if (pendingAnchor === void 0) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const label = pendingAnchor.textParts.join("");
|
|
1308
|
+
elements.push({
|
|
1309
|
+
type: "link",
|
|
1310
|
+
url: pendingAnchor.href,
|
|
1311
|
+
text: label.length > 0 ? label : pendingAnchor.href,
|
|
1312
|
+
style: styleOrUndefined(pendingAnchor.style)
|
|
1313
|
+
});
|
|
1314
|
+
pendingAnchor = void 0;
|
|
1315
|
+
};
|
|
1316
|
+
for (const node of nodes) {
|
|
1317
|
+
if (pendingAnchor !== void 0) {
|
|
1318
|
+
if (node.type === "html" && /^<\/a\s*>$/i.test(node.value.trim())) {
|
|
1319
|
+
flushPendingAnchor();
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
if (node.type === "text") {
|
|
1323
|
+
pendingAnchor.textParts.push(node.value);
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
if (node.type === "break") {
|
|
1327
|
+
pendingAnchor.textParts.push("\n");
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
pendingAnchor.textParts.push(toString2(node));
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
if (node.type === "html") {
|
|
1334
|
+
const trimmed = node.value.trim();
|
|
1335
|
+
const openOnly = /^<a\b([^>]{0,500})>\s*$/i.exec(trimmed);
|
|
1336
|
+
if (openOnly !== null) {
|
|
1337
|
+
const hrefMatch = /href\s*=\s*["']([^"']{0,3000})["']/i.exec(openOnly[1] ?? "");
|
|
1338
|
+
const href = hrefMatch?.[1];
|
|
1339
|
+
if (href !== void 0 && isValidLinkUrl(href)) {
|
|
1340
|
+
pendingAnchor = { href, style: baseStyle, textParts: [] };
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
elements.push(...emitInlineHtml(node.value, baseStyle, ctx));
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
switch (node.type) {
|
|
1348
|
+
case "text": {
|
|
1349
|
+
elements.push(...emitTextWithMentionsAndEmoji(node, baseStyle, ctx));
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
case "strong": {
|
|
1353
|
+
const inner = emitPhrasing(node.children, ctx, { ...baseStyle, bold: true });
|
|
1354
|
+
elements.push(...inner.elements);
|
|
1355
|
+
hoistedImages.push(...inner.hoistedImages);
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
case "emphasis": {
|
|
1359
|
+
const inner = emitPhrasing(node.children, ctx, {
|
|
1360
|
+
...baseStyle,
|
|
1361
|
+
italic: true
|
|
1362
|
+
});
|
|
1363
|
+
elements.push(...inner.elements);
|
|
1364
|
+
hoistedImages.push(...inner.hoistedImages);
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
case "delete": {
|
|
1368
|
+
const inner = emitPhrasing(node.children, ctx, { ...baseStyle, strike: true });
|
|
1369
|
+
elements.push(...inner.elements);
|
|
1370
|
+
hoistedImages.push(...inner.hoistedImages);
|
|
1371
|
+
break;
|
|
1372
|
+
}
|
|
1373
|
+
case "inlineCode": {
|
|
1374
|
+
const el = {
|
|
1375
|
+
type: "text",
|
|
1376
|
+
text: node.value,
|
|
1377
|
+
style: styleOrUndefined({ ...baseStyle, code: true })
|
|
1378
|
+
};
|
|
1379
|
+
elements.push(el);
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
case "break": {
|
|
1383
|
+
elements.push({ type: "text", text: "\n", style: styleOrUndefined(baseStyle) });
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
case "link": {
|
|
1387
|
+
elements.push(...emitLink(node, ctx, baseStyle));
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
case "linkReference": {
|
|
1391
|
+
elements.push(...emitLinkReference(node, ctx, baseStyle));
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
case "image": {
|
|
1395
|
+
const url = node.url;
|
|
1396
|
+
if (isPublicImageUrl(url)) {
|
|
1397
|
+
hoistedImages.push({
|
|
1398
|
+
url,
|
|
1399
|
+
alt: node.alt ?? "",
|
|
1400
|
+
title: node.title ?? void 0
|
|
1401
|
+
});
|
|
1402
|
+
elements.push({
|
|
1403
|
+
type: "link",
|
|
1404
|
+
url,
|
|
1405
|
+
text: node.alt && node.alt.length > 0 ? node.alt : url,
|
|
1406
|
+
style: styleOrUndefined(baseStyle)
|
|
1407
|
+
});
|
|
1408
|
+
} else {
|
|
1409
|
+
ctx.degradations.add({
|
|
1410
|
+
nodeType: "image",
|
|
1411
|
+
from: "image",
|
|
1412
|
+
to: "link",
|
|
1413
|
+
reason: "Image URL is not a public http(s) png/jpg/gif \u22643000 chars",
|
|
1414
|
+
path: ctx.path
|
|
1415
|
+
});
|
|
1416
|
+
elements.push({
|
|
1417
|
+
type: "link",
|
|
1418
|
+
url: url.length > 0 ? url : "#",
|
|
1419
|
+
text: node.alt && node.alt.length > 0 ? node.alt : url,
|
|
1420
|
+
style: styleOrUndefined(baseStyle)
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1425
|
+
case "imageReference": {
|
|
1426
|
+
const def = ctx.definitions.get(node.identifier.toLowerCase());
|
|
1427
|
+
if (def === void 0) {
|
|
1428
|
+
elements.push({
|
|
1429
|
+
type: "text",
|
|
1430
|
+
text: node.alt ?? node.identifier,
|
|
1431
|
+
style: styleOrUndefined(baseStyle)
|
|
1432
|
+
});
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
const asImage = emitPhrasing(
|
|
1436
|
+
[
|
|
1437
|
+
{
|
|
1438
|
+
type: "image",
|
|
1439
|
+
url: def.url,
|
|
1440
|
+
alt: node.alt ?? void 0,
|
|
1441
|
+
title: def.title ?? void 0
|
|
1442
|
+
}
|
|
1443
|
+
],
|
|
1444
|
+
ctx,
|
|
1445
|
+
baseStyle
|
|
1446
|
+
);
|
|
1447
|
+
elements.push(...asImage.elements);
|
|
1448
|
+
hoistedImages.push(...asImage.hoistedImages);
|
|
1449
|
+
break;
|
|
1450
|
+
}
|
|
1451
|
+
case "footnoteReference": {
|
|
1452
|
+
const idx = ctx.footnoteOrder.indexOf(node.identifier);
|
|
1453
|
+
const n = idx >= 0 ? idx + 1 : 1;
|
|
1454
|
+
elements.push({
|
|
1455
|
+
type: "text",
|
|
1456
|
+
text: `[${String(n)}]`,
|
|
1457
|
+
style: styleOrUndefined(baseStyle)
|
|
1458
|
+
});
|
|
1459
|
+
break;
|
|
1460
|
+
}
|
|
1461
|
+
case "inlineMath": {
|
|
1462
|
+
elements.push({
|
|
1463
|
+
type: "text",
|
|
1464
|
+
text: node.value,
|
|
1465
|
+
style: styleOrUndefined({ ...baseStyle, code: true })
|
|
1466
|
+
});
|
|
1467
|
+
ctx.degradations.add({
|
|
1468
|
+
nodeType: "inlineMath",
|
|
1469
|
+
from: "inlineMath",
|
|
1470
|
+
to: "style.code",
|
|
1471
|
+
reason: "Math rendering not configured; emitted as inline code",
|
|
1472
|
+
path: ctx.path
|
|
1473
|
+
});
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
default: {
|
|
1477
|
+
const text = toString2(node);
|
|
1478
|
+
if (text.length > 0) {
|
|
1479
|
+
elements.push({ type: "text", text, style: styleOrUndefined(baseStyle) });
|
|
1480
|
+
}
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
flushPendingAnchor();
|
|
1486
|
+
return { elements: coalesceText(elements), hoistedImages };
|
|
1487
|
+
}
|
|
1488
|
+
function phrasingToPlain(nodes) {
|
|
1489
|
+
return toString2({ type: "paragraph", children: nodes });
|
|
1490
|
+
}
|
|
1491
|
+
function isPublicImageUrl(url) {
|
|
1492
|
+
if (url.length === 0 || url.length > IMAGE_URL_MAX_CHARS) {
|
|
1493
|
+
return false;
|
|
1494
|
+
}
|
|
1495
|
+
if (!HTTP_URL_RE.test(url)) {
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/i.test(
|
|
1499
|
+
url
|
|
1500
|
+
)) {
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
return IMAGE_EXT_RE.test(url);
|
|
1504
|
+
}
|
|
1505
|
+
function countHoistableImages(nodes, ctx) {
|
|
1506
|
+
let count = 0;
|
|
1507
|
+
for (const node of nodes) {
|
|
1508
|
+
switch (node.type) {
|
|
1509
|
+
case "image": {
|
|
1510
|
+
if (isPublicImageUrl(node.url)) {
|
|
1511
|
+
count += 1;
|
|
1512
|
+
}
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
case "imageReference": {
|
|
1516
|
+
const def = ctx.definitions.get(node.identifier.toLowerCase());
|
|
1517
|
+
if (def !== void 0 && isPublicImageUrl(def.url)) {
|
|
1518
|
+
count += 1;
|
|
1519
|
+
}
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
case "strong":
|
|
1523
|
+
case "emphasis":
|
|
1524
|
+
case "delete": {
|
|
1525
|
+
count += countHoistableImages(node.children, ctx);
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
case "text":
|
|
1529
|
+
case "inlineCode":
|
|
1530
|
+
case "break":
|
|
1531
|
+
case "link":
|
|
1532
|
+
case "linkReference":
|
|
1533
|
+
case "footnoteReference":
|
|
1534
|
+
case "html":
|
|
1535
|
+
case "inlineMath":
|
|
1536
|
+
break;
|
|
1537
|
+
default: {
|
|
1538
|
+
break;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return count;
|
|
1543
|
+
}
|
|
1544
|
+
function truncateLabel(label, max) {
|
|
1545
|
+
if (label.length <= max) {
|
|
1546
|
+
return { text: label, truncated: false };
|
|
1547
|
+
}
|
|
1548
|
+
return { text: `${label.slice(0, Math.max(0, max - 1))}${ELLIPSIS}`, truncated: true };
|
|
1549
|
+
}
|
|
1550
|
+
function hoistedImageBlock(h, ctx) {
|
|
1551
|
+
return {
|
|
1552
|
+
type: "image",
|
|
1553
|
+
image_url: h.url,
|
|
1554
|
+
alt_text: clampImageText(h.alt.length > 0 ? h.alt : "image", ctx, "alt_text"),
|
|
1555
|
+
title: h.title !== void 0 && h.title.length > 0 ? { type: "plain_text", text: clampImageText(h.title, ctx, "title") } : void 0
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
function clampSectionText(text, ctx, nodeType) {
|
|
1559
|
+
if (text.length <= SECTION_TEXT_MAX_CHARS) {
|
|
1560
|
+
return text;
|
|
1561
|
+
}
|
|
1562
|
+
ctx.degradations.add({
|
|
1563
|
+
nodeType,
|
|
1564
|
+
from: `${String(text.length)} chars`,
|
|
1565
|
+
to: `${String(SECTION_TEXT_MAX_CHARS)} chars`,
|
|
1566
|
+
reason: "Section text truncated to 3000 chars",
|
|
1567
|
+
path: ctx.path
|
|
1568
|
+
});
|
|
1569
|
+
return text.slice(0, SECTION_TEXT_MAX_CHARS);
|
|
1570
|
+
}
|
|
1571
|
+
function preformattedRichText(text, language) {
|
|
1572
|
+
return {
|
|
1573
|
+
type: "rich_text",
|
|
1574
|
+
elements: [
|
|
1575
|
+
{
|
|
1576
|
+
type: "rich_text_preformatted",
|
|
1577
|
+
elements: [{ type: "text", text: text.length > 0 ? text : " " }],
|
|
1578
|
+
border: 0,
|
|
1579
|
+
language
|
|
1580
|
+
}
|
|
1581
|
+
]
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function clampImageText(value, ctx, field) {
|
|
1585
|
+
if (value.length <= IMAGE_ALT_MAX_CHARS) {
|
|
1586
|
+
return value;
|
|
1587
|
+
}
|
|
1588
|
+
ctx.degradations.add({
|
|
1589
|
+
nodeType: "image",
|
|
1590
|
+
from: field,
|
|
1591
|
+
to: "truncated",
|
|
1592
|
+
reason: `Image ${field} truncated to ${String(IMAGE_ALT_MAX_CHARS)} chars`,
|
|
1593
|
+
path: ctx.path
|
|
1594
|
+
});
|
|
1595
|
+
return value.slice(0, IMAGE_ALT_MAX_CHARS);
|
|
1596
|
+
}
|
|
1597
|
+
function styleOrUndefined(style) {
|
|
1598
|
+
if (style.bold === true || style.italic === true || style.strike === true || style.code === true || style.underline === true) {
|
|
1599
|
+
return style;
|
|
1600
|
+
}
|
|
1601
|
+
return void 0;
|
|
1602
|
+
}
|
|
1603
|
+
var MENTION_RE = /(?<![A-Za-z0-9._-])@([a-zA-Z0-9._-]{1,64})|(?<![A-Za-z0-9_-])#([a-zA-Z0-9_-]{1,80})/g;
|
|
1604
|
+
function phrasingNeedsMentionResolution(nodes, ctx) {
|
|
1605
|
+
const resolvers = ctx.config.mentionResolvers;
|
|
1606
|
+
if (resolvers?.resolveUser === void 0 && resolvers?.resolveChannel === void 0) {
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1609
|
+
const plain = phrasingToPlain(nodes);
|
|
1610
|
+
MENTION_RE.lastIndex = 0;
|
|
1611
|
+
return MENTION_RE.test(plain);
|
|
1612
|
+
}
|
|
1613
|
+
function emitTextWithMentionsAndEmoji(node, style, ctx) {
|
|
1614
|
+
const resolvers = ctx.config.mentionResolvers;
|
|
1615
|
+
const canUser = resolvers?.resolveUser !== void 0;
|
|
1616
|
+
const canChannel = resolvers?.resolveChannel !== void 0;
|
|
1617
|
+
if (!canUser && !canChannel) {
|
|
1618
|
+
return emitTextWithEmoji(node.value, style);
|
|
1619
|
+
}
|
|
1620
|
+
const value = node.value;
|
|
1621
|
+
const out = [];
|
|
1622
|
+
MENTION_RE.lastIndex = 0;
|
|
1623
|
+
let last = 0;
|
|
1624
|
+
let match = MENTION_RE.exec(value);
|
|
1625
|
+
while (match !== null) {
|
|
1626
|
+
const start = match.index;
|
|
1627
|
+
if (start > last) {
|
|
1628
|
+
out.push(...emitTextWithEmoji(value.slice(last, start), style));
|
|
1629
|
+
}
|
|
1630
|
+
const userHandle = match[1];
|
|
1631
|
+
const channelName = match[2];
|
|
1632
|
+
if (userHandle !== void 0 && canUser) {
|
|
1633
|
+
const userId = resolvers.resolveUser?.(userHandle);
|
|
1634
|
+
if (userId !== void 0 && userId.length > 0) {
|
|
1635
|
+
out.push({ type: "user", user_id: userId, style: styleOrUndefined(style) });
|
|
1636
|
+
} else {
|
|
1637
|
+
out.push(...emitTextWithEmoji(match[0], style));
|
|
1638
|
+
}
|
|
1639
|
+
} else if (channelName !== void 0 && canChannel) {
|
|
1640
|
+
const channelId = resolvers.resolveChannel?.(channelName);
|
|
1641
|
+
if (channelId !== void 0 && channelId.length > 0) {
|
|
1642
|
+
out.push({ type: "channel", channel_id: channelId, style: styleOrUndefined(style) });
|
|
1643
|
+
} else {
|
|
1644
|
+
out.push(...emitTextWithEmoji(match[0], style));
|
|
1645
|
+
}
|
|
1646
|
+
} else {
|
|
1647
|
+
out.push(...emitTextWithEmoji(match[0], style));
|
|
1648
|
+
}
|
|
1649
|
+
last = start + match[0].length;
|
|
1650
|
+
match = MENTION_RE.exec(value);
|
|
1651
|
+
}
|
|
1652
|
+
if (last < value.length) {
|
|
1653
|
+
out.push(...emitTextWithEmoji(value.slice(last), style));
|
|
1654
|
+
}
|
|
1655
|
+
if (out.length === 0) {
|
|
1656
|
+
out.push(...emitTextWithEmoji(value, style));
|
|
1657
|
+
}
|
|
1658
|
+
return out;
|
|
1659
|
+
}
|
|
1660
|
+
function emitTextWithEmoji(value, style) {
|
|
1661
|
+
const out = [];
|
|
1662
|
+
EMOJI_SHORTCODE_RE.lastIndex = 0;
|
|
1663
|
+
let last = 0;
|
|
1664
|
+
let match = EMOJI_SHORTCODE_RE.exec(value);
|
|
1665
|
+
while (match !== null) {
|
|
1666
|
+
const start = match.index;
|
|
1667
|
+
if (start > last) {
|
|
1668
|
+
out.push({ type: "text", text: value.slice(last, start), style: styleOrUndefined(style) });
|
|
1669
|
+
}
|
|
1670
|
+
const name = match[1] ?? "";
|
|
1671
|
+
if (KNOWN_SLACK_EMOJI.has(name)) {
|
|
1672
|
+
const emoji = { type: "emoji", name };
|
|
1673
|
+
out.push(emoji);
|
|
1674
|
+
} else {
|
|
1675
|
+
out.push({ type: "text", text: match[0], style: styleOrUndefined(style) });
|
|
1676
|
+
}
|
|
1677
|
+
last = start + match[0].length;
|
|
1678
|
+
match = EMOJI_SHORTCODE_RE.exec(value);
|
|
1679
|
+
}
|
|
1680
|
+
if (last < value.length) {
|
|
1681
|
+
out.push({ type: "text", text: value.slice(last), style: styleOrUndefined(style) });
|
|
1682
|
+
}
|
|
1683
|
+
if (out.length === 0) {
|
|
1684
|
+
out.push({ type: "text", text: value, style: styleOrUndefined(style) });
|
|
1685
|
+
}
|
|
1686
|
+
return out;
|
|
1687
|
+
}
|
|
1688
|
+
function emitLink(node, ctx, style) {
|
|
1689
|
+
const url = node.url;
|
|
1690
|
+
const text = phrasingToPlain(node.children);
|
|
1691
|
+
if (!isValidLinkUrl(url)) {
|
|
1692
|
+
ctx.degradations.add({
|
|
1693
|
+
nodeType: "link",
|
|
1694
|
+
from: "link",
|
|
1695
|
+
to: "text",
|
|
1696
|
+
reason: "Invalid or oversized URL",
|
|
1697
|
+
path: ctx.path
|
|
1698
|
+
});
|
|
1699
|
+
const suffix = url.length > 0 ? ` (${url})` : "";
|
|
1700
|
+
return [{ type: "text", text: `${text}${suffix}`, style: styleOrUndefined(style) }];
|
|
1701
|
+
}
|
|
1702
|
+
const link = {
|
|
1703
|
+
type: "link",
|
|
1704
|
+
url,
|
|
1705
|
+
text: text.length > 0 ? text : url,
|
|
1706
|
+
style: styleOrUndefined(style)
|
|
1707
|
+
};
|
|
1708
|
+
return [link];
|
|
1709
|
+
}
|
|
1710
|
+
function emitLinkReference(node, ctx, style) {
|
|
1711
|
+
const def = ctx.definitions.get(node.identifier.toLowerCase());
|
|
1712
|
+
if (def === void 0) {
|
|
1713
|
+
return [
|
|
1714
|
+
{
|
|
1715
|
+
type: "text",
|
|
1716
|
+
text: phrasingToPlain(node.children) || node.identifier,
|
|
1717
|
+
style: styleOrUndefined(style)
|
|
1718
|
+
}
|
|
1719
|
+
];
|
|
1720
|
+
}
|
|
1721
|
+
return emitLink(
|
|
1722
|
+
{
|
|
1723
|
+
type: "link",
|
|
1724
|
+
url: def.url,
|
|
1725
|
+
title: def.title ?? void 0,
|
|
1726
|
+
children: node.children
|
|
1727
|
+
},
|
|
1728
|
+
ctx,
|
|
1729
|
+
style
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
function isValidLinkUrl(url) {
|
|
1733
|
+
if (url.length === 0 || url.length > IMAGE_URL_MAX_CHARS) {
|
|
1734
|
+
return false;
|
|
1735
|
+
}
|
|
1736
|
+
return HTTP_URL_RE.test(url) || url.startsWith("#") || url.startsWith("/");
|
|
1737
|
+
}
|
|
1738
|
+
var INLINE_TAG_RE = /<\/?(b|strong|i|em|s|del|code|br|a)(\s+[^>]{0,200})?>|[^<]+/gi;
|
|
1739
|
+
function emitInlineHtml(html, style, ctx) {
|
|
1740
|
+
const out = [];
|
|
1741
|
+
const stack = [];
|
|
1742
|
+
let current = style;
|
|
1743
|
+
let openAnchor;
|
|
1744
|
+
INLINE_TAG_RE.lastIndex = 0;
|
|
1745
|
+
let m = INLINE_TAG_RE.exec(html);
|
|
1746
|
+
let matched = false;
|
|
1747
|
+
while (m !== null) {
|
|
1748
|
+
matched = true;
|
|
1749
|
+
const token = m[0];
|
|
1750
|
+
if (token.startsWith("<")) {
|
|
1751
|
+
const tag = (m[1] ?? "").toLowerCase();
|
|
1752
|
+
const closing = token.startsWith("</");
|
|
1753
|
+
if (tag === "br") {
|
|
1754
|
+
if (openAnchor !== void 0) {
|
|
1755
|
+
openAnchor.textParts.push("\n");
|
|
1756
|
+
} else {
|
|
1757
|
+
out.push({ type: "text", text: "\n", style: styleOrUndefined(current) });
|
|
1758
|
+
}
|
|
1759
|
+
} else if (closing) {
|
|
1760
|
+
if (tag === "a" && openAnchor !== void 0) {
|
|
1761
|
+
const label = openAnchor.textParts.join("");
|
|
1762
|
+
out.push({
|
|
1763
|
+
type: "link",
|
|
1764
|
+
url: openAnchor.href,
|
|
1765
|
+
text: label.length > 0 ? label : openAnchor.href,
|
|
1766
|
+
style: styleOrUndefined(openAnchor.style)
|
|
1767
|
+
});
|
|
1768
|
+
openAnchor = void 0;
|
|
1769
|
+
}
|
|
1770
|
+
stack.pop();
|
|
1771
|
+
current = stack.length > 0 ? stack[stack.length - 1]?.style ?? style : style;
|
|
1772
|
+
} else if (tag === "a") {
|
|
1773
|
+
const hrefMatch = /href\s*=\s*["']([^"']{0,3000})["']/i.exec(token);
|
|
1774
|
+
const href = hrefMatch?.[1];
|
|
1775
|
+
stack.push({ tag, style: current });
|
|
1776
|
+
if (href !== void 0 && isValidLinkUrl(href)) {
|
|
1777
|
+
openAnchor = { href, style: current, textParts: [] };
|
|
1778
|
+
}
|
|
1779
|
+
} else {
|
|
1780
|
+
const next = { ...current };
|
|
1781
|
+
if (tag === "b" || tag === "strong") next.bold = true;
|
|
1782
|
+
if (tag === "i" || tag === "em") next.italic = true;
|
|
1783
|
+
if (tag === "s" || tag === "del") next.strike = true;
|
|
1784
|
+
if (tag === "code") next.code = true;
|
|
1785
|
+
stack.push({ tag, style: next });
|
|
1786
|
+
current = next;
|
|
1787
|
+
}
|
|
1788
|
+
} else if (token.length > 0) {
|
|
1789
|
+
if (openAnchor !== void 0) {
|
|
1790
|
+
openAnchor.textParts.push(token);
|
|
1791
|
+
} else {
|
|
1792
|
+
out.push({ type: "text", text: token, style: styleOrUndefined(current) });
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
m = INLINE_TAG_RE.exec(html);
|
|
1796
|
+
}
|
|
1797
|
+
if (openAnchor !== void 0) {
|
|
1798
|
+
const label = openAnchor.textParts.join("");
|
|
1799
|
+
out.push({
|
|
1800
|
+
type: "link",
|
|
1801
|
+
url: openAnchor.href,
|
|
1802
|
+
text: label.length > 0 ? label : openAnchor.href,
|
|
1803
|
+
style: styleOrUndefined(openAnchor.style)
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
if (!matched) {
|
|
1807
|
+
ctx.degradations.add({
|
|
1808
|
+
nodeType: "html",
|
|
1809
|
+
from: "html",
|
|
1810
|
+
to: "text",
|
|
1811
|
+
reason: "Unrecognized inline HTML stripped to text",
|
|
1812
|
+
path: ctx.path
|
|
1813
|
+
});
|
|
1814
|
+
const stripped = html.replace(/<[^>]{0,200}>/g, "");
|
|
1815
|
+
if (stripped.length > 0) {
|
|
1816
|
+
out.push({ type: "text", text: stripped, style: styleOrUndefined(style) });
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return out;
|
|
1820
|
+
}
|
|
1821
|
+
function coalesceText(elements) {
|
|
1822
|
+
const out = [];
|
|
1823
|
+
for (const el of elements) {
|
|
1824
|
+
const prev = out[out.length - 1];
|
|
1825
|
+
if (el.type === "text" && prev !== void 0 && prev.type === "text" && JSON.stringify(prev.style) === JSON.stringify(el.style)) {
|
|
1826
|
+
out[out.length - 1] = { type: "text", text: prev.text + el.text, style: prev.style };
|
|
1827
|
+
} else {
|
|
1828
|
+
out.push(el);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return out;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/adapters/blockquote-adapter.ts
|
|
1835
|
+
var ADMONITION_RE = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i;
|
|
1836
|
+
var ADMONITION_META = {
|
|
1837
|
+
NOTE: { emoji: "\u{1F4DD}", title: "Note" },
|
|
1838
|
+
TIP: { emoji: "\u{1F4A1}", title: "Tip" },
|
|
1839
|
+
IMPORTANT: { emoji: "\u2757", title: "Important" },
|
|
1840
|
+
WARNING: { emoji: "\u26A0\uFE0F", title: "Warning" },
|
|
1841
|
+
CAUTION: { emoji: "\u{1F6AB}", title: "Caution" }
|
|
1842
|
+
};
|
|
1843
|
+
var BlockquoteAdapter = class {
|
|
1844
|
+
match(node, _ctx) {
|
|
1845
|
+
return node.type === "blockquote";
|
|
1846
|
+
}
|
|
1847
|
+
plan(node, _ctx) {
|
|
1848
|
+
const quote = node;
|
|
1849
|
+
return [
|
|
1850
|
+
{
|
|
1851
|
+
budget: { blocks: 1 },
|
|
1852
|
+
emit: (ctx) => {
|
|
1853
|
+
const elements = flattenQuote(quote, ctx);
|
|
1854
|
+
const block = {
|
|
1855
|
+
type: "rich_text",
|
|
1856
|
+
elements: [{ type: "rich_text_quote", elements, border: 0 }]
|
|
1857
|
+
};
|
|
1858
|
+
return [block];
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
function flattenQuote(quote, ctx) {
|
|
1865
|
+
const parts = [];
|
|
1866
|
+
let first = true;
|
|
1867
|
+
for (const child of quote.children) {
|
|
1868
|
+
if (child.type === "paragraph") {
|
|
1869
|
+
let children = child.children;
|
|
1870
|
+
if (first && children[0]?.type === "text") {
|
|
1871
|
+
const textNode = children[0];
|
|
1872
|
+
const m = ADMONITION_RE.exec(textNode.value);
|
|
1873
|
+
if (m !== null) {
|
|
1874
|
+
const kind = (m[1] ?? "NOTE").toUpperCase();
|
|
1875
|
+
const meta = ADMONITION_META[kind];
|
|
1876
|
+
parts.push({
|
|
1877
|
+
type: "text",
|
|
1878
|
+
text: `${meta.emoji}${meta.title}
|
|
1879
|
+
`,
|
|
1880
|
+
style: { bold: true }
|
|
1881
|
+
});
|
|
1882
|
+
const rest = textNode.value.slice(m[0].length);
|
|
1883
|
+
children = [
|
|
1884
|
+
...rest.length > 0 ? [{ ...textNode, value: rest }] : [],
|
|
1885
|
+
...children.slice(1)
|
|
1886
|
+
];
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
first = false;
|
|
1890
|
+
const inline = emitPhrasing(children, ctx);
|
|
1891
|
+
if (parts.length > 0 && inline.elements.length > 0) {
|
|
1892
|
+
parts.push({ type: "text", text: "\n" });
|
|
1893
|
+
}
|
|
1894
|
+
parts.push(...inline.elements);
|
|
1895
|
+
} else if (child.type === "list") {
|
|
1896
|
+
ctx.degradations.add({
|
|
1897
|
+
nodeType: "blockquote",
|
|
1898
|
+
from: "list",
|
|
1899
|
+
to: "bullet_lines",
|
|
1900
|
+
reason: "Nested list inside blockquote flattened",
|
|
1901
|
+
path: ctx.path
|
|
1902
|
+
});
|
|
1903
|
+
if (parts.length > 0) parts.push({ type: "text", text: "\n" });
|
|
1904
|
+
for (const item of child.children) {
|
|
1905
|
+
parts.push({ type: "text", text: `\u2022 ${toString3(item)}
|
|
1906
|
+
` });
|
|
1907
|
+
}
|
|
1908
|
+
} else if (child.type === "code") {
|
|
1909
|
+
ctx.degradations.add({
|
|
1910
|
+
nodeType: "blockquote",
|
|
1911
|
+
from: "code",
|
|
1912
|
+
to: "text",
|
|
1913
|
+
reason: "Nested code inside blockquote flattened to text",
|
|
1914
|
+
path: ctx.path
|
|
1915
|
+
});
|
|
1916
|
+
if (parts.length > 0) parts.push({ type: "text", text: "\n" });
|
|
1917
|
+
parts.push({ type: "text", text: child.value, style: { code: true } });
|
|
1918
|
+
} else {
|
|
1919
|
+
ctx.degradations.add({
|
|
1920
|
+
nodeType: "blockquote",
|
|
1921
|
+
from: child.type,
|
|
1922
|
+
to: "text",
|
|
1923
|
+
reason: "Nested block inside blockquote flattened",
|
|
1924
|
+
path: ctx.path
|
|
1925
|
+
});
|
|
1926
|
+
if (parts.length > 0) parts.push({ type: "text", text: "\n" });
|
|
1927
|
+
parts.push({ type: "text", text: toString3(child) });
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return parts.length > 0 ? parts : [{ type: "text", text: " " }];
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// src/adapters/code-fence-adapter.ts
|
|
1934
|
+
var CodeFenceAdapter = class {
|
|
1935
|
+
match(node, _ctx) {
|
|
1936
|
+
return node.type === "code";
|
|
1937
|
+
}
|
|
1938
|
+
plan(node, _ctx) {
|
|
1939
|
+
const code = node;
|
|
1940
|
+
const hasLang = code.lang !== null && code.lang !== void 0 && code.lang.length > 0;
|
|
1941
|
+
return [
|
|
1942
|
+
{
|
|
1943
|
+
requires: ["preformattedLanguage"],
|
|
1944
|
+
budget: { blocks: 1 },
|
|
1945
|
+
emit: () => [preformatted(code, true)]
|
|
1946
|
+
},
|
|
1947
|
+
{
|
|
1948
|
+
budget: { blocks: 1 },
|
|
1949
|
+
emit: (emitCtx) => {
|
|
1950
|
+
if (hasLang) {
|
|
1951
|
+
emitCtx.degradations.add({
|
|
1952
|
+
nodeType: "code",
|
|
1953
|
+
from: "preformatted.language",
|
|
1954
|
+
to: "preformatted",
|
|
1955
|
+
reason: "preformattedLanguage capability unavailable; omitting language",
|
|
1956
|
+
path: emitCtx.path
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
return [preformatted(code, false)];
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
];
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
function preformatted(code, withLang) {
|
|
1966
|
+
const language = withLang && code.lang !== null && code.lang !== void 0 && code.lang.length > 0 ? code.lang.toLowerCase() : void 0;
|
|
1967
|
+
return preformattedRichText(code.value, language);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// src/adapters/heading-adapter.ts
|
|
1971
|
+
var HeadingAdapter = class {
|
|
1972
|
+
match(node, _ctx) {
|
|
1973
|
+
return node.type === "heading";
|
|
1974
|
+
}
|
|
1975
|
+
plan(node, ctx) {
|
|
1976
|
+
const heading = node;
|
|
1977
|
+
const hoistCount = countHoistableImages(heading.children, ctx);
|
|
1978
|
+
const needsRich = headingNeedsRichText(heading, ctx);
|
|
1979
|
+
if (!needsRich) {
|
|
1980
|
+
return [
|
|
1981
|
+
{
|
|
1982
|
+
requires: ["headerLevel"],
|
|
1983
|
+
budget: { blocks: 1 },
|
|
1984
|
+
emit: () => {
|
|
1985
|
+
return emitPlainHeader(heading, true);
|
|
1986
|
+
}
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
budget: { blocks: 1 },
|
|
1990
|
+
emit: (emitCtx) => {
|
|
1991
|
+
emitCtx.degradations.add({
|
|
1992
|
+
nodeType: "heading",
|
|
1993
|
+
from: "header.level",
|
|
1994
|
+
to: "header",
|
|
1995
|
+
reason: "headerLevel capability unavailable; omitting level",
|
|
1996
|
+
path: emitCtx.path
|
|
1997
|
+
});
|
|
1998
|
+
return emitPlainHeader(heading, false);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
];
|
|
2002
|
+
}
|
|
2003
|
+
const richCandidates = [
|
|
2004
|
+
{
|
|
2005
|
+
budget: { blocks: 1 + hoistCount },
|
|
2006
|
+
emit: (emitCtx) => {
|
|
2007
|
+
return emitRichHeading(heading, emitCtx, true);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
];
|
|
2011
|
+
if (hoistCount > 0) {
|
|
2012
|
+
richCandidates.push({
|
|
2013
|
+
budget: { blocks: 1 },
|
|
2014
|
+
emit: (emitCtx) => {
|
|
2015
|
+
emitCtx.degradations.add({
|
|
2016
|
+
nodeType: "heading",
|
|
2017
|
+
from: "rich_text+images",
|
|
2018
|
+
to: "rich_text",
|
|
2019
|
+
reason: "Budget exhausted for hoisted heading images; emitting text only",
|
|
2020
|
+
path: emitCtx.path
|
|
2021
|
+
});
|
|
2022
|
+
return emitRichHeading(heading, emitCtx, false);
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
return richCandidates;
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
function headingNeedsRichText(heading, ctx) {
|
|
2030
|
+
if (heading.depth > HEADER_LEVEL_MAX) {
|
|
2031
|
+
return true;
|
|
2032
|
+
}
|
|
2033
|
+
const plain = phrasingToPlain(heading.children);
|
|
2034
|
+
if (plain.length > HEADER_TEXT_MAX_CHARS) {
|
|
2035
|
+
return true;
|
|
2036
|
+
}
|
|
2037
|
+
if (phrasingNeedsMentionResolution(heading.children, ctx)) {
|
|
2038
|
+
return true;
|
|
2039
|
+
}
|
|
2040
|
+
return heading.children.some(
|
|
2041
|
+
(c) => c.type === "link" || c.type === "inlineCode" || c.type === "strong" || c.type === "emphasis" || c.type === "delete" || c.type === "image" || c.type === "imageReference" || c.type === "html"
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
function emitPlainHeader(heading, withLevel) {
|
|
2045
|
+
const plain = phrasingToPlain(heading.children);
|
|
2046
|
+
const block = {
|
|
2047
|
+
type: "header",
|
|
2048
|
+
text: { type: "plain_text", text: plain.length > 0 ? plain : " " },
|
|
2049
|
+
level: withLevel ? heading.depth : void 0
|
|
2050
|
+
};
|
|
2051
|
+
return [block];
|
|
2052
|
+
}
|
|
2053
|
+
function emitRichHeading(heading, ctx, hoistImages) {
|
|
2054
|
+
const plain = phrasingToPlain(heading.children);
|
|
2055
|
+
const depth = heading.depth;
|
|
2056
|
+
if (depth > HEADER_LEVEL_MAX) {
|
|
2057
|
+
ctx.degradations.add({
|
|
2058
|
+
nodeType: "heading",
|
|
2059
|
+
from: `header.h${String(depth)}`,
|
|
2060
|
+
to: "rich_text.bold",
|
|
2061
|
+
reason: `Heading level ${String(depth)} exceeds max ${String(HEADER_LEVEL_MAX)}`,
|
|
2062
|
+
path: ctx.path
|
|
2063
|
+
});
|
|
2064
|
+
} else if (plain.length > HEADER_TEXT_MAX_CHARS) {
|
|
2065
|
+
ctx.degradations.add({
|
|
2066
|
+
nodeType: "heading",
|
|
2067
|
+
from: "header",
|
|
2068
|
+
to: "rich_text.bold",
|
|
2069
|
+
reason: "Heading exceeds 150 plain chars",
|
|
2070
|
+
path: ctx.path
|
|
2071
|
+
});
|
|
2072
|
+
} else {
|
|
2073
|
+
ctx.degradations.add({
|
|
2074
|
+
nodeType: "heading",
|
|
2075
|
+
from: "header",
|
|
2076
|
+
to: "rich_text.bold",
|
|
2077
|
+
reason: "Heading contains inline content unsupported by header (styles, links, images, or mentions)",
|
|
2078
|
+
path: ctx.path
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
const inline = emitPhrasing(heading.children, ctx, { bold: true });
|
|
2082
|
+
const rich = {
|
|
2083
|
+
type: "rich_text",
|
|
2084
|
+
elements: [
|
|
2085
|
+
{
|
|
2086
|
+
type: "rich_text_section",
|
|
2087
|
+
elements: inline.elements.length > 0 ? inline.elements : [{ type: "text", text: " ", style: { bold: true } }]
|
|
2088
|
+
}
|
|
2089
|
+
]
|
|
2090
|
+
};
|
|
2091
|
+
const blocks = [rich];
|
|
2092
|
+
if (!hoistImages) {
|
|
2093
|
+
return blocks;
|
|
2094
|
+
}
|
|
2095
|
+
for (const h of inline.hoistedImages) {
|
|
2096
|
+
blocks.push(hoistedImageBlock(h, ctx));
|
|
2097
|
+
}
|
|
2098
|
+
return blocks;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
// src/adapters/html-block-adapter.ts
|
|
2102
|
+
var DETAILS_RE = /<details\b[^>]{0,200}>\s*<summary\b[^>]{0,200}>([\s\S]{0,500}?)<\/summary>([\s\S]{0,8000}?)<\/details>/i;
|
|
2103
|
+
var HtmlBlockAdapter = class {
|
|
2104
|
+
match(node, _ctx) {
|
|
2105
|
+
return node.type === "html";
|
|
2106
|
+
}
|
|
2107
|
+
plan(node, _ctx) {
|
|
2108
|
+
const html = node;
|
|
2109
|
+
const details = DETAILS_RE.exec(html.value);
|
|
2110
|
+
if (details !== null) {
|
|
2111
|
+
const title = stripTags(details[1] ?? "Details").trim();
|
|
2112
|
+
const body = stripTags(details[2] ?? "").trim();
|
|
2113
|
+
return [
|
|
2114
|
+
{
|
|
2115
|
+
requires: ["containerBlock"],
|
|
2116
|
+
budget: { blocks: 1 },
|
|
2117
|
+
emit: (ctx) => {
|
|
2118
|
+
const child = {
|
|
2119
|
+
type: "section",
|
|
2120
|
+
text: {
|
|
2121
|
+
type: "mrkdwn",
|
|
2122
|
+
text: clampSectionText(body.length > 0 ? body : " ", ctx, "html.details.body")
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
const block = {
|
|
2126
|
+
type: "container",
|
|
2127
|
+
title: {
|
|
2128
|
+
type: "plain_text",
|
|
2129
|
+
text: clampContainerTitle(title.length > 0 ? title : "Details", ctx)
|
|
2130
|
+
},
|
|
2131
|
+
child_blocks: [child].slice(0, CONTAINER_CHILDREN_MAX),
|
|
2132
|
+
is_collapsible: true
|
|
2133
|
+
};
|
|
2134
|
+
return [block];
|
|
2135
|
+
}
|
|
2136
|
+
},
|
|
2137
|
+
{
|
|
2138
|
+
budget: { blocks: 1 },
|
|
2139
|
+
emit: (ctx) => {
|
|
2140
|
+
ctx.degradations.add({
|
|
2141
|
+
nodeType: "html.details",
|
|
2142
|
+
from: "container",
|
|
2143
|
+
to: "section",
|
|
2144
|
+
reason: "containerBlock unavailable",
|
|
2145
|
+
path: ctx.path
|
|
2146
|
+
});
|
|
2147
|
+
return [
|
|
2148
|
+
{
|
|
2149
|
+
type: "section",
|
|
2150
|
+
text: {
|
|
2151
|
+
type: "mrkdwn",
|
|
2152
|
+
text: clampSectionText(`*${title}*
|
|
2153
|
+
${body}`, ctx, "html.details.section")
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
];
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
];
|
|
2160
|
+
}
|
|
2161
|
+
return [
|
|
2162
|
+
{
|
|
2163
|
+
budget: { blocks: 1 },
|
|
2164
|
+
emit: (ctx) => {
|
|
2165
|
+
ctx.degradations.add({
|
|
2166
|
+
nodeType: "html",
|
|
2167
|
+
from: "html",
|
|
2168
|
+
to: "section",
|
|
2169
|
+
reason: "Unsupported block HTML stripped to text",
|
|
2170
|
+
path: ctx.path
|
|
2171
|
+
});
|
|
2172
|
+
const text = stripTags(html.value).trim();
|
|
2173
|
+
if (text.length === 0) {
|
|
2174
|
+
return [];
|
|
2175
|
+
}
|
|
2176
|
+
return [
|
|
2177
|
+
{
|
|
2178
|
+
type: "section",
|
|
2179
|
+
text: { type: "mrkdwn", text: clampSectionText(text, ctx, "html.section") }
|
|
2180
|
+
}
|
|
2181
|
+
];
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
];
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
function stripTags(html) {
|
|
2188
|
+
return html.replace(/<[^>]{0,200}>/g, "");
|
|
2189
|
+
}
|
|
2190
|
+
function clampContainerTitle(text, ctx) {
|
|
2191
|
+
if (text.length <= CONTAINER_TITLE_MAX_CHARS) {
|
|
2192
|
+
return text;
|
|
2193
|
+
}
|
|
2194
|
+
ctx.degradations.add({
|
|
2195
|
+
nodeType: "html.details",
|
|
2196
|
+
from: "container.title",
|
|
2197
|
+
to: "truncated",
|
|
2198
|
+
reason: `Container title truncated to ${String(CONTAINER_TITLE_MAX_CHARS)} chars`,
|
|
2199
|
+
path: ctx.path
|
|
2200
|
+
});
|
|
2201
|
+
return text.slice(0, CONTAINER_TITLE_MAX_CHARS);
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// src/adapters/list-adapter.ts
|
|
2205
|
+
import { toString as toString4 } from "mdast-util-to-string";
|
|
2206
|
+
var ListAdapter = class {
|
|
2207
|
+
match(node, _ctx) {
|
|
2208
|
+
if (node.type !== "list") {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
return !isTaskList(node);
|
|
2212
|
+
}
|
|
2213
|
+
plan(node, ctx) {
|
|
2214
|
+
const list = node;
|
|
2215
|
+
const hoistCount = countHoistableImagesInList(list, ctx);
|
|
2216
|
+
const candidates = [
|
|
2217
|
+
{
|
|
2218
|
+
budget: { blocks: 1 + hoistCount },
|
|
2219
|
+
emit: (emitCtx) => {
|
|
2220
|
+
return emitListBlocks(list, emitCtx, true);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
];
|
|
2224
|
+
if (hoistCount > 0) {
|
|
2225
|
+
candidates.push({
|
|
2226
|
+
budget: { blocks: 1 },
|
|
2227
|
+
emit: (emitCtx) => {
|
|
2228
|
+
emitCtx.degradations.add({
|
|
2229
|
+
nodeType: "list",
|
|
2230
|
+
from: "rich_text+images",
|
|
2231
|
+
to: "rich_text",
|
|
2232
|
+
reason: "Budget exhausted for hoisted list images; emitting text only",
|
|
2233
|
+
path: emitCtx.path
|
|
2234
|
+
});
|
|
2235
|
+
return emitListBlocks(list, emitCtx, false);
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
return candidates;
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
function emitListBlocks(list, emitCtx, hoistImages) {
|
|
2243
|
+
const { elements, hoistedImages } = emitListTree(list, 0, emitCtx);
|
|
2244
|
+
const block = { type: "rich_text", elements };
|
|
2245
|
+
const blocks = [block];
|
|
2246
|
+
if (!hoistImages) {
|
|
2247
|
+
return blocks;
|
|
2248
|
+
}
|
|
2249
|
+
for (const h of hoistedImages) {
|
|
2250
|
+
blocks.push(hoistedImageBlock(h, emitCtx));
|
|
2251
|
+
}
|
|
2252
|
+
return blocks;
|
|
2253
|
+
}
|
|
2254
|
+
function isTaskList(list) {
|
|
2255
|
+
return list.children.some((item) => item.checked === true || item.checked === false);
|
|
2256
|
+
}
|
|
2257
|
+
function countHoistableImagesInList(list, ctx) {
|
|
2258
|
+
let count = 0;
|
|
2259
|
+
for (const item of list.children) {
|
|
2260
|
+
count += countHoistableImages(collectListItemPhrasing(item), ctx);
|
|
2261
|
+
for (const child of item.children) {
|
|
2262
|
+
if (child.type === "list") {
|
|
2263
|
+
count += countHoistableImagesInList(child, ctx);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
return count;
|
|
2268
|
+
}
|
|
2269
|
+
function emitListTree(list, indent, ctx) {
|
|
2270
|
+
let effectiveIndent = indent;
|
|
2271
|
+
if (effectiveIndent > RICH_TEXT_LIST_INDENT_MAX) {
|
|
2272
|
+
ctx.degradations.add({
|
|
2273
|
+
nodeType: "list",
|
|
2274
|
+
from: `indent:${String(effectiveIndent)}`,
|
|
2275
|
+
to: `indent:${String(RICH_TEXT_LIST_INDENT_MAX)}`,
|
|
2276
|
+
reason: "List indent clamped at 6",
|
|
2277
|
+
path: ctx.path
|
|
2278
|
+
});
|
|
2279
|
+
effectiveIndent = RICH_TEXT_LIST_INDENT_MAX;
|
|
2280
|
+
}
|
|
2281
|
+
const sections = [];
|
|
2282
|
+
const nested = [];
|
|
2283
|
+
const hoistedImages = [];
|
|
2284
|
+
for (const item of list.children) {
|
|
2285
|
+
const { section, childLists, hoistedImages: itemImages } = emitListItem(item, ctx);
|
|
2286
|
+
sections.push(section);
|
|
2287
|
+
hoistedImages.push(...itemImages);
|
|
2288
|
+
for (const child of childLists) {
|
|
2289
|
+
const nestedResult = emitListTree(child, effectiveIndent + 1, ctx);
|
|
2290
|
+
nested.push(...nestedResult.elements);
|
|
2291
|
+
hoistedImages.push(...nestedResult.hoistedImages);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const primary = {
|
|
2295
|
+
type: "rich_text_list",
|
|
2296
|
+
style: list.ordered === true ? "ordered" : "bullet",
|
|
2297
|
+
elements: sections.length > 0 ? sections : [{ type: "rich_text_section", elements: [{ type: "text", text: " " }] }],
|
|
2298
|
+
indent: effectiveIndent,
|
|
2299
|
+
offset: list.ordered === true && list.start !== null && list.start !== void 0 && list.start > 1 ? list.start - 1 : void 0
|
|
2300
|
+
};
|
|
2301
|
+
return { elements: [primary, ...nested], hoistedImages };
|
|
2302
|
+
}
|
|
2303
|
+
function collectListItemPhrasing(item) {
|
|
2304
|
+
const phrasing = [];
|
|
2305
|
+
let sawBlock = false;
|
|
2306
|
+
for (const child of item.children) {
|
|
2307
|
+
if (child.type === "paragraph") {
|
|
2308
|
+
if (sawBlock) {
|
|
2309
|
+
phrasing.push({ type: "text", value: "\n" });
|
|
2310
|
+
}
|
|
2311
|
+
phrasing.push(...child.children);
|
|
2312
|
+
sawBlock = true;
|
|
2313
|
+
} else if (child.type !== "list") {
|
|
2314
|
+
sawBlock = true;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return phrasing;
|
|
2318
|
+
}
|
|
2319
|
+
function emitListItem(item, ctx) {
|
|
2320
|
+
const phrasing = [];
|
|
2321
|
+
const childLists = [];
|
|
2322
|
+
let sawBlock = false;
|
|
2323
|
+
for (const child of item.children) {
|
|
2324
|
+
if (child.type === "paragraph") {
|
|
2325
|
+
if (sawBlock) {
|
|
2326
|
+
phrasing.push({ type: "text", value: "\n" });
|
|
2327
|
+
}
|
|
2328
|
+
phrasing.push(...child.children);
|
|
2329
|
+
sawBlock = true;
|
|
2330
|
+
} else if (child.type === "list") {
|
|
2331
|
+
childLists.push(child);
|
|
2332
|
+
} else {
|
|
2333
|
+
ctx.degradations.add({
|
|
2334
|
+
nodeType: "listItem",
|
|
2335
|
+
from: child.type,
|
|
2336
|
+
to: "text",
|
|
2337
|
+
reason: "Block child of list item flattened into text",
|
|
2338
|
+
path: ctx.path
|
|
2339
|
+
});
|
|
2340
|
+
const text = toString4(child);
|
|
2341
|
+
if (text.length > 0) {
|
|
2342
|
+
phrasing.push({ type: "text", value: text });
|
|
2343
|
+
}
|
|
2344
|
+
sawBlock = true;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
const inline = emitPhrasing(phrasing, ctx);
|
|
2348
|
+
return {
|
|
2349
|
+
section: {
|
|
2350
|
+
type: "rich_text_section",
|
|
2351
|
+
elements: inline.elements.length > 0 ? inline.elements : [{ type: "text", text: " " }]
|
|
2352
|
+
},
|
|
2353
|
+
childLists,
|
|
2354
|
+
hoistedImages: [...inline.hoistedImages]
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// src/adapters/math-block-adapter.ts
|
|
2359
|
+
var MathBlockAdapter = class {
|
|
2360
|
+
match(node, _ctx) {
|
|
2361
|
+
return node.type === "math";
|
|
2362
|
+
}
|
|
2363
|
+
plan(node, _ctx) {
|
|
2364
|
+
const value = "value" in node && typeof node.value === "string" ? node.value : "";
|
|
2365
|
+
return [
|
|
2366
|
+
{
|
|
2367
|
+
requires: ["preformattedLanguage"],
|
|
2368
|
+
budget: { blocks: 1 },
|
|
2369
|
+
emit: (ctx) => {
|
|
2370
|
+
return [preformattedMath(value, true, ctx)];
|
|
2371
|
+
}
|
|
2372
|
+
},
|
|
2373
|
+
{
|
|
2374
|
+
budget: { blocks: 1 },
|
|
2375
|
+
emit: (ctx) => {
|
|
2376
|
+
ctx.degradations.add({
|
|
2377
|
+
nodeType: "math",
|
|
2378
|
+
from: "preformatted.language",
|
|
2379
|
+
to: "preformatted",
|
|
2380
|
+
reason: "preformattedLanguage capability unavailable; omitting language",
|
|
2381
|
+
path: ctx.path
|
|
2382
|
+
});
|
|
2383
|
+
return [preformattedMath(value, false, ctx)];
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
];
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
function preformattedMath(value, withLang, ctx) {
|
|
2390
|
+
ctx.degradations.add({
|
|
2391
|
+
nodeType: "math",
|
|
2392
|
+
from: "math",
|
|
2393
|
+
to: "rich_text_preformatted",
|
|
2394
|
+
reason: "No math renderer configured; emitting TeX source",
|
|
2395
|
+
path: ctx.path
|
|
2396
|
+
});
|
|
2397
|
+
return preformattedRichText(value, withLang ? "latex" : void 0);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// src/adapters/mermaid-fence-adapter.ts
|
|
2401
|
+
var MermaidFenceAdapter = class {
|
|
2402
|
+
pieParser;
|
|
2403
|
+
xyParser;
|
|
2404
|
+
constructor(deps) {
|
|
2405
|
+
this.pieParser = deps.pieParser;
|
|
2406
|
+
this.xyParser = deps.xyParser;
|
|
2407
|
+
}
|
|
2408
|
+
match(node, _ctx) {
|
|
2409
|
+
return node.type === "code" && (node.lang ?? "").toLowerCase() === "mermaid";
|
|
2410
|
+
}
|
|
2411
|
+
plan(node, ctx) {
|
|
2412
|
+
const code = node;
|
|
2413
|
+
const source = code.value;
|
|
2414
|
+
const kind = detectMermaidKind(source);
|
|
2415
|
+
const candidates = [];
|
|
2416
|
+
const probeCtx = ctx.withDegradations(new RecordingDegradationCollector());
|
|
2417
|
+
if (kind === "pie") {
|
|
2418
|
+
const parsed = this.pieParser.parse(source);
|
|
2419
|
+
if (parsed.ok && parsed.value.kind === "pie") {
|
|
2420
|
+
const probe = tryPieBlock(parsed.value, probeCtx);
|
|
2421
|
+
if (probe !== void 0) {
|
|
2422
|
+
const ast = parsed.value;
|
|
2423
|
+
candidates.push({
|
|
2424
|
+
requires: ["dataVisualization"],
|
|
2425
|
+
budget: { blocks: 1, chart: true },
|
|
2426
|
+
emit: (emitCtx) => {
|
|
2427
|
+
const block = tryPieBlock(ast, emitCtx);
|
|
2428
|
+
return block !== void 0 ? [block] : [];
|
|
2429
|
+
}
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
} else if (kind === "xychart-beta") {
|
|
2434
|
+
const parsed = this.xyParser.parse(source);
|
|
2435
|
+
if (parsed.ok && parsed.value.kind === "xychart-beta") {
|
|
2436
|
+
const probe = tryXyBlock(parsed.value, probeCtx);
|
|
2437
|
+
if (probe !== void 0) {
|
|
2438
|
+
const ast = parsed.value;
|
|
2439
|
+
candidates.push({
|
|
2440
|
+
requires: ["dataVisualization"],
|
|
2441
|
+
budget: { blocks: 1, chart: true },
|
|
2442
|
+
emit: (emitCtx) => {
|
|
2443
|
+
const block = tryXyBlock(ast, emitCtx);
|
|
2444
|
+
return block !== void 0 ? [block] : [];
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
candidates.push({
|
|
2451
|
+
budget: { blocks: 1 },
|
|
2452
|
+
emit: async (emitCtx) => {
|
|
2453
|
+
const rendered = await tryRenderers(source, emitCtx);
|
|
2454
|
+
if (rendered !== void 0) {
|
|
2455
|
+
return [rendered];
|
|
2456
|
+
}
|
|
2457
|
+
emitCtx.degradations.add({
|
|
2458
|
+
nodeType: "code.mermaid",
|
|
2459
|
+
from: kind,
|
|
2460
|
+
to: "rich_text_preformatted",
|
|
2461
|
+
reason: "No native chart / renderer image; emitting mermaid source",
|
|
2462
|
+
path: emitCtx.path
|
|
2463
|
+
});
|
|
2464
|
+
return [preformattedMermaid(source, emitCtx)];
|
|
2465
|
+
}
|
|
2466
|
+
});
|
|
2467
|
+
return candidates;
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
function detectMermaidKind(source) {
|
|
2471
|
+
const first = source.split(/\r?\n/).map((l) => l.replace(/%%[^\n]{0,500}/, "").trim()).find((l) => l.length > 0);
|
|
2472
|
+
if (first === void 0) {
|
|
2473
|
+
return "other";
|
|
2474
|
+
}
|
|
2475
|
+
if (/^pie\b/i.test(first)) {
|
|
2476
|
+
return "pie";
|
|
2477
|
+
}
|
|
2478
|
+
if (/^xychart-beta\b/i.test(first)) {
|
|
2479
|
+
return "xychart-beta";
|
|
2480
|
+
}
|
|
2481
|
+
return "other";
|
|
2482
|
+
}
|
|
2483
|
+
function tryPieBlock(ast, ctx) {
|
|
2484
|
+
if (ast.segments.length > CHART_SEGMENTS_MAX) {
|
|
2485
|
+
ctx.degradations.add({
|
|
2486
|
+
nodeType: "code.mermaid.pie",
|
|
2487
|
+
from: "data_visualization",
|
|
2488
|
+
to: "fallback",
|
|
2489
|
+
reason: `Pie has ${String(ast.segments.length)} segments (max ${String(CHART_SEGMENTS_MAX)})`,
|
|
2490
|
+
path: ctx.path
|
|
2491
|
+
});
|
|
2492
|
+
return void 0;
|
|
2493
|
+
}
|
|
2494
|
+
const segments = ast.segments.map((s) => {
|
|
2495
|
+
const t = truncateLabel(s.label, CHART_LABEL_MAX_CHARS);
|
|
2496
|
+
if (t.truncated) {
|
|
2497
|
+
ctx.degradations.add({
|
|
2498
|
+
nodeType: "code.mermaid.pie",
|
|
2499
|
+
from: s.label,
|
|
2500
|
+
to: t.text,
|
|
2501
|
+
reason: "Pie label truncated to 20 chars",
|
|
2502
|
+
path: ctx.path
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
return { label: t.text, value: s.value };
|
|
2506
|
+
});
|
|
2507
|
+
const titleRaw = ast.title ?? (ctx.precedingHeading !== void 0 && ctx.precedingHeading.length > 0 ? ctx.precedingHeading : "Chart");
|
|
2508
|
+
return {
|
|
2509
|
+
type: "data_visualization",
|
|
2510
|
+
title: truncateChartTitle(titleRaw, ctx, "code.mermaid.pie"),
|
|
2511
|
+
chart: { type: "pie", segments }
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function tryXyBlock(ast, ctx) {
|
|
2515
|
+
const categories = ast.categories.map((c) => {
|
|
2516
|
+
const t = truncateLabel(c, CHART_LABEL_MAX_CHARS);
|
|
2517
|
+
if (t.truncated) {
|
|
2518
|
+
ctx.degradations.add({
|
|
2519
|
+
nodeType: "code.mermaid.xychart",
|
|
2520
|
+
from: c,
|
|
2521
|
+
to: t.text,
|
|
2522
|
+
reason: "Category label truncated to 20 chars",
|
|
2523
|
+
path: ctx.path
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
return t.text;
|
|
2527
|
+
});
|
|
2528
|
+
if (categories.length > CHART_POINTS_MAX) {
|
|
2529
|
+
ctx.degradations.add({
|
|
2530
|
+
nodeType: "code.mermaid.xychart",
|
|
2531
|
+
from: "data_visualization",
|
|
2532
|
+
to: "fallback",
|
|
2533
|
+
reason: `xychart has ${String(categories.length)} points (max ${String(CHART_POINTS_MAX)})`,
|
|
2534
|
+
path: ctx.path
|
|
2535
|
+
});
|
|
2536
|
+
return void 0;
|
|
2537
|
+
}
|
|
2538
|
+
let chartType = "bar";
|
|
2539
|
+
let seriesValues = ast.bars;
|
|
2540
|
+
if (ast.bars.length > 0 && ast.lines.length > 0) {
|
|
2541
|
+
ctx.degradations.add({
|
|
2542
|
+
nodeType: "code.mermaid.xychart",
|
|
2543
|
+
from: "mixed bar+line",
|
|
2544
|
+
to: "bar",
|
|
2545
|
+
reason: "Slack charts are single-type; preferring bar",
|
|
2546
|
+
path: ctx.path
|
|
2547
|
+
});
|
|
2548
|
+
} else if (ast.bars.length === 0) {
|
|
2549
|
+
chartType = "line";
|
|
2550
|
+
seriesValues = ast.lines;
|
|
2551
|
+
}
|
|
2552
|
+
if (seriesValues.length === 0 || seriesValues.length > CHART_SERIES_MAX) {
|
|
2553
|
+
ctx.degradations.add({
|
|
2554
|
+
nodeType: "code.mermaid.xychart",
|
|
2555
|
+
from: "data_visualization",
|
|
2556
|
+
to: "fallback",
|
|
2557
|
+
reason: `Invalid series count (${String(seriesValues.length)})`,
|
|
2558
|
+
path: ctx.path
|
|
2559
|
+
});
|
|
2560
|
+
return void 0;
|
|
2561
|
+
}
|
|
2562
|
+
for (const [i, values] of seriesValues.entries()) {
|
|
2563
|
+
if (values.length !== categories.length) {
|
|
2564
|
+
ctx.degradations.add({
|
|
2565
|
+
nodeType: "code.mermaid.xychart",
|
|
2566
|
+
from: `series[${String(i)}].length=${String(values.length)}`,
|
|
2567
|
+
to: `categories.length=${String(categories.length)}`,
|
|
2568
|
+
reason: "xychart series length mismatched categories; padded/truncated by index",
|
|
2569
|
+
path: ctx.path
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
const series = seriesValues.map((values, i) => ({
|
|
2574
|
+
name: truncateLabel(`Series ${String(i + 1)}`, CHART_LABEL_MAX_CHARS).text,
|
|
2575
|
+
data: categories.map((label, idx) => ({
|
|
2576
|
+
label,
|
|
2577
|
+
value: values[idx] ?? 0
|
|
2578
|
+
}))
|
|
2579
|
+
}));
|
|
2580
|
+
const titleRaw = ast.title ?? (ctx.precedingHeading !== void 0 && ctx.precedingHeading.length > 0 ? ctx.precedingHeading : "Chart");
|
|
2581
|
+
return {
|
|
2582
|
+
type: "data_visualization",
|
|
2583
|
+
title: truncateChartTitle(titleRaw, ctx, "code.mermaid.xychart"),
|
|
2584
|
+
chart: {
|
|
2585
|
+
type: chartType,
|
|
2586
|
+
series,
|
|
2587
|
+
axis_config: {
|
|
2588
|
+
categories,
|
|
2589
|
+
x_label: ast.xLabel !== void 0 ? truncateChartTitle(ast.xLabel, ctx, "code.mermaid.xychart") : void 0,
|
|
2590
|
+
y_label: ast.yLabel !== void 0 ? truncateChartTitle(ast.yLabel, ctx, "code.mermaid.xychart") : void 0
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function truncateChartTitle(raw, ctx, nodeType) {
|
|
2596
|
+
const t = truncateLabel(raw, CHART_TITLE_MAX_CHARS);
|
|
2597
|
+
if (t.truncated) {
|
|
2598
|
+
ctx.degradations.add({
|
|
2599
|
+
nodeType,
|
|
2600
|
+
from: raw,
|
|
2601
|
+
to: t.text,
|
|
2602
|
+
reason: `Chart title truncated to ${String(CHART_TITLE_MAX_CHARS)} chars`,
|
|
2603
|
+
path: ctx.path
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
return t.text;
|
|
2607
|
+
}
|
|
2608
|
+
async function tryRenderers(source, ctx) {
|
|
2609
|
+
const req = {
|
|
2610
|
+
lang: "mermaid",
|
|
2611
|
+
source,
|
|
2612
|
+
altText: "mermaid diagram"
|
|
2613
|
+
};
|
|
2614
|
+
for (const renderer of ctx.config.renderers) {
|
|
2615
|
+
if (!renderer.canRender(req)) {
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
const result = await renderer.render(req);
|
|
2619
|
+
if (result === null) {
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
if (result.kind === "url") {
|
|
2623
|
+
if (result.imageUrl.length > IMAGE_URL_MAX_CHARS) {
|
|
2624
|
+
ctx.degradations.add({
|
|
2625
|
+
nodeType: "code.mermaid",
|
|
2626
|
+
from: "image.url",
|
|
2627
|
+
to: "skip",
|
|
2628
|
+
reason: "Renderer URL exceeds 3000 chars",
|
|
2629
|
+
path: ctx.path
|
|
2630
|
+
});
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
return {
|
|
2634
|
+
type: "image",
|
|
2635
|
+
image_url: result.imageUrl,
|
|
2636
|
+
alt_text: clampImageText(result.altText, ctx, "alt_text")
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
const uploader = ctx.config.uploader;
|
|
2640
|
+
if (uploader === void 0) {
|
|
2641
|
+
ctx.degradations.add({
|
|
2642
|
+
nodeType: "code.mermaid",
|
|
2643
|
+
from: "image.bytes",
|
|
2644
|
+
to: "skip",
|
|
2645
|
+
reason: "Bytes renderer requires options.uploader",
|
|
2646
|
+
path: ctx.path
|
|
2647
|
+
});
|
|
2648
|
+
continue;
|
|
2649
|
+
}
|
|
2650
|
+
const uploaded = await uploader.upload(result);
|
|
2651
|
+
return {
|
|
2652
|
+
type: "image",
|
|
2653
|
+
alt_text: clampImageText(result.altText, ctx, "alt_text"),
|
|
2654
|
+
slack_file: { id: uploaded.fileId }
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
return void 0;
|
|
2658
|
+
}
|
|
2659
|
+
function preformattedMermaid(source, ctx) {
|
|
2660
|
+
const withLang = ctx.config.capabilities.preformattedLanguage;
|
|
2661
|
+
if (!withLang) {
|
|
2662
|
+
ctx.degradations.add({
|
|
2663
|
+
nodeType: "code.mermaid",
|
|
2664
|
+
from: "preformatted.language",
|
|
2665
|
+
to: "preformatted",
|
|
2666
|
+
reason: "preformattedLanguage capability unavailable; omitting language",
|
|
2667
|
+
path: ctx.path
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
return preformattedRichText(source, withLang ? "mermaid" : void 0);
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// src/adapters/paragraph-adapter.ts
|
|
2674
|
+
var ParagraphAdapter = class {
|
|
2675
|
+
match(node, _ctx) {
|
|
2676
|
+
return node.type === "paragraph";
|
|
2677
|
+
}
|
|
2678
|
+
plan(node, ctx) {
|
|
2679
|
+
const paragraph = node;
|
|
2680
|
+
const loneImage = paragraph.children.length === 1 && paragraph.children[0]?.type === "image" ? paragraph.children[0] : void 0;
|
|
2681
|
+
const hoistCount = loneImage !== void 0 ? 0 : countHoistableImages(paragraph.children, ctx);
|
|
2682
|
+
const candidates = [
|
|
2683
|
+
{
|
|
2684
|
+
budget: { blocks: 1 + hoistCount },
|
|
2685
|
+
emit: (emitCtx) => {
|
|
2686
|
+
return emitParagraph(paragraph, emitCtx, loneImage, true);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
];
|
|
2690
|
+
if (hoistCount > 0) {
|
|
2691
|
+
candidates.push({
|
|
2692
|
+
budget: { blocks: 1 },
|
|
2693
|
+
emit: (emitCtx) => {
|
|
2694
|
+
emitCtx.degradations.add({
|
|
2695
|
+
nodeType: "paragraph",
|
|
2696
|
+
from: "rich_text+images",
|
|
2697
|
+
to: "rich_text",
|
|
2698
|
+
reason: "Budget exhausted for hoisted paragraph images; emitting text only",
|
|
2699
|
+
path: emitCtx.path
|
|
2700
|
+
});
|
|
2701
|
+
return emitParagraph(paragraph, emitCtx, loneImage, false);
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
return candidates;
|
|
2706
|
+
}
|
|
2707
|
+
};
|
|
2708
|
+
function emitParagraph(paragraph, emitCtx, loneImage, hoistImages) {
|
|
2709
|
+
if (loneImage !== void 0 && loneImage.type === "image") {
|
|
2710
|
+
const img = loneImage;
|
|
2711
|
+
if (isPublicImageUrl(img.url)) {
|
|
2712
|
+
const altRaw = img.alt ?? "image";
|
|
2713
|
+
const alt = clampImageText(altRaw.length > 0 ? altRaw : "image", emitCtx, "alt_text");
|
|
2714
|
+
const block = {
|
|
2715
|
+
type: "image",
|
|
2716
|
+
image_url: img.url,
|
|
2717
|
+
alt_text: alt,
|
|
2718
|
+
title: img.title !== null && img.title !== void 0 && img.title.length > 0 ? {
|
|
2719
|
+
type: "plain_text",
|
|
2720
|
+
text: clampImageText(img.title, emitCtx, "title")
|
|
2721
|
+
} : void 0
|
|
2722
|
+
};
|
|
2723
|
+
return [block];
|
|
2724
|
+
}
|
|
2725
|
+
emitCtx.degradations.add({
|
|
2726
|
+
nodeType: "image",
|
|
2727
|
+
from: "image",
|
|
2728
|
+
to: "section.link",
|
|
2729
|
+
reason: "Non-public or invalid image URL",
|
|
2730
|
+
path: emitCtx.path
|
|
2731
|
+
});
|
|
2732
|
+
const linkText = `<${img.url}|${img.alt ?? img.url}>`;
|
|
2733
|
+
return [
|
|
2734
|
+
{
|
|
2735
|
+
type: "section",
|
|
2736
|
+
text: {
|
|
2737
|
+
type: "mrkdwn",
|
|
2738
|
+
text: clampSectionText(linkText, emitCtx, "section")
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
];
|
|
2742
|
+
}
|
|
2743
|
+
const inline = emitPhrasing(paragraph.children, emitCtx);
|
|
2744
|
+
const blocks = [];
|
|
2745
|
+
const rich = {
|
|
2746
|
+
type: "rich_text",
|
|
2747
|
+
elements: [
|
|
2748
|
+
{
|
|
2749
|
+
type: "rich_text_section",
|
|
2750
|
+
elements: inline.elements.length > 0 ? inline.elements : [{ type: "text", text: " " }]
|
|
2751
|
+
}
|
|
2752
|
+
]
|
|
2753
|
+
};
|
|
2754
|
+
blocks.push(rich);
|
|
2755
|
+
if (!hoistImages) {
|
|
2756
|
+
return blocks;
|
|
2757
|
+
}
|
|
2758
|
+
for (const h of inline.hoistedImages) {
|
|
2759
|
+
blocks.push(hoistedImageBlock(h, emitCtx));
|
|
2760
|
+
}
|
|
2761
|
+
return blocks;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// src/adapters/skip-adapter.ts
|
|
2765
|
+
var SkipAdapter = class {
|
|
2766
|
+
match(node, _ctx) {
|
|
2767
|
+
const t = node.type;
|
|
2768
|
+
return t === "definition" || t === "yaml" || t === "toml" || t === "footnoteDefinition";
|
|
2769
|
+
}
|
|
2770
|
+
plan(node, _ctx) {
|
|
2771
|
+
return [
|
|
2772
|
+
{
|
|
2773
|
+
emit: (ctx) => {
|
|
2774
|
+
const t = node.type;
|
|
2775
|
+
if (t === "yaml" || t === "toml") {
|
|
2776
|
+
ctx.degradations.add({
|
|
2777
|
+
nodeType: t,
|
|
2778
|
+
from: "frontmatter",
|
|
2779
|
+
to: "skip",
|
|
2780
|
+
reason: "Frontmatter skipped by default",
|
|
2781
|
+
path: ctx.path
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
return [];
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
];
|
|
2788
|
+
}
|
|
2789
|
+
};
|
|
2790
|
+
|
|
2791
|
+
// src/adapters/table-adapter.ts
|
|
2792
|
+
import { toString as toString5 } from "mdast-util-to-string";
|
|
2793
|
+
var TableAdapter = class {
|
|
2794
|
+
match(node, _ctx) {
|
|
2795
|
+
return node.type === "table";
|
|
2796
|
+
}
|
|
2797
|
+
plan(node, ctx) {
|
|
2798
|
+
const table = node;
|
|
2799
|
+
const rowCount = table.children.length;
|
|
2800
|
+
const colCount = table.children[0]?.children.length ?? 0;
|
|
2801
|
+
const cellChars = countEmittedCellChars(table, ctx);
|
|
2802
|
+
if (colCount > TABLE_MAX_COLS) {
|
|
2803
|
+
return markdownThenAscii(table, cellChars, "Table exceeds 20 columns");
|
|
2804
|
+
}
|
|
2805
|
+
if (rowCount <= TABLE_PREFERRED_MAX_ROWS) {
|
|
2806
|
+
return [
|
|
2807
|
+
{
|
|
2808
|
+
requires: ["tableBlock"],
|
|
2809
|
+
budget: { blocks: 1, tableChars: cellChars },
|
|
2810
|
+
emit: (ctx2) => {
|
|
2811
|
+
return [buildTableBlock(table, ctx2)];
|
|
2812
|
+
}
|
|
2813
|
+
},
|
|
2814
|
+
...markdownThenAscii(table, cellChars, "tableBlock unavailable or char budget")
|
|
2815
|
+
];
|
|
2816
|
+
}
|
|
2817
|
+
const dataRows = Math.max(0, rowCount - 1);
|
|
2818
|
+
if (dataRows <= DATA_TABLE_MAX_DATA_ROWS) {
|
|
2819
|
+
const preferredTable = truncateRows(table, TABLE_PREFERRED_MAX_ROWS);
|
|
2820
|
+
const preferredChars = countEmittedCellChars(preferredTable, ctx);
|
|
2821
|
+
return [
|
|
2822
|
+
{
|
|
2823
|
+
requires: ["dataTable"],
|
|
2824
|
+
budget: { blocks: 1, dataTableChars: cellChars },
|
|
2825
|
+
emit: (emitCtx) => {
|
|
2826
|
+
emitCtx.degradations.add({
|
|
2827
|
+
nodeType: "table",
|
|
2828
|
+
from: "table",
|
|
2829
|
+
to: "data_table",
|
|
2830
|
+
reason: `Table has ${String(rowCount)} rows (>${String(TABLE_PREFERRED_MAX_ROWS)}); using data_table`,
|
|
2831
|
+
path: emitCtx.path
|
|
2832
|
+
});
|
|
2833
|
+
return [buildDataTable(table, emitCtx)];
|
|
2834
|
+
}
|
|
2835
|
+
},
|
|
2836
|
+
{
|
|
2837
|
+
requires: ["tableBlock"],
|
|
2838
|
+
budget: { blocks: 1, tableChars: preferredChars },
|
|
2839
|
+
emit: (emitCtx) => {
|
|
2840
|
+
emitCtx.degradations.add({
|
|
2841
|
+
nodeType: "table",
|
|
2842
|
+
from: "data_table",
|
|
2843
|
+
to: "table",
|
|
2844
|
+
reason: "dataTable unavailable; truncating to table block capacity",
|
|
2845
|
+
path: emitCtx.path
|
|
2846
|
+
});
|
|
2847
|
+
return [buildTableBlock(preferredTable, emitCtx)];
|
|
2848
|
+
}
|
|
2849
|
+
},
|
|
2850
|
+
...markdownThenAscii(table, cellChars, "native table unavailable")
|
|
2851
|
+
];
|
|
2852
|
+
}
|
|
2853
|
+
const dataChunk = truncateRows(table, DATA_TABLE_MAX_DATA_ROWS + 1);
|
|
2854
|
+
const dataChunkChars = countEmittedCellChars(dataChunk, ctx);
|
|
2855
|
+
return [
|
|
2856
|
+
{
|
|
2857
|
+
requires: ["dataTable"],
|
|
2858
|
+
budget: { blocks: 1, dataTableChars: dataChunkChars },
|
|
2859
|
+
emit: (emitCtx) => {
|
|
2860
|
+
emitCtx.degradations.add({
|
|
2861
|
+
nodeType: "table",
|
|
2862
|
+
from: "table",
|
|
2863
|
+
to: "data_table.split",
|
|
2864
|
+
reason: `Table has ${String(dataRows)} data rows; emitting first ${String(DATA_TABLE_MAX_DATA_ROWS)}`,
|
|
2865
|
+
path: emitCtx.path
|
|
2866
|
+
});
|
|
2867
|
+
return [buildDataTable(dataChunk, emitCtx)];
|
|
2868
|
+
}
|
|
2869
|
+
},
|
|
2870
|
+
...markdownThenAscii(table, cellChars, "oversized table")
|
|
2871
|
+
];
|
|
2872
|
+
}
|
|
2873
|
+
};
|
|
2874
|
+
function markdownThenAscii(table, cellChars, reason) {
|
|
2875
|
+
const md = toPipeMarkdown(table);
|
|
2876
|
+
return [
|
|
2877
|
+
{
|
|
2878
|
+
requires: ["markdownBlock"],
|
|
2879
|
+
budget: { blocks: 1, markdownChars: md.length },
|
|
2880
|
+
emit: (ctx) => {
|
|
2881
|
+
ctx.degradations.add({
|
|
2882
|
+
nodeType: "table",
|
|
2883
|
+
from: "native_table",
|
|
2884
|
+
to: "markdown",
|
|
2885
|
+
reason,
|
|
2886
|
+
path: ctx.path
|
|
2887
|
+
});
|
|
2888
|
+
const block = { type: "markdown", text: md };
|
|
2889
|
+
return [block];
|
|
2890
|
+
}
|
|
2891
|
+
},
|
|
2892
|
+
{
|
|
2893
|
+
budget: { blocks: 1 },
|
|
2894
|
+
emit: (ctx) => {
|
|
2895
|
+
ctx.degradations.add({
|
|
2896
|
+
nodeType: "table",
|
|
2897
|
+
from: "markdown",
|
|
2898
|
+
to: "ascii_preformatted",
|
|
2899
|
+
reason: `${reason}; markdown unavailable (chars\u2248${String(cellChars)})`,
|
|
2900
|
+
path: ctx.path
|
|
2901
|
+
});
|
|
2902
|
+
return [asciiTableBlock(table)];
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
];
|
|
2906
|
+
}
|
|
2907
|
+
function buildTableBlock(table, ctx) {
|
|
2908
|
+
const rows = table.children.map(
|
|
2909
|
+
(row, rowIndex) => row.children.map((cell) => cellToSlack(cell, ctx, rowIndex === 0))
|
|
2910
|
+
);
|
|
2911
|
+
return {
|
|
2912
|
+
type: "table",
|
|
2913
|
+
rows,
|
|
2914
|
+
column_settings: columnSettings(table)
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function buildDataTable(table, ctx) {
|
|
2918
|
+
const captionRaw = ctx.precedingHeading !== void 0 && ctx.precedingHeading.length > 0 ? ctx.precedingHeading : "Table";
|
|
2919
|
+
const caption = captionRaw.slice(0, 150);
|
|
2920
|
+
if (captionRaw.length > 150) {
|
|
2921
|
+
ctx.degradations.add({
|
|
2922
|
+
nodeType: "table",
|
|
2923
|
+
from: "data_table.caption",
|
|
2924
|
+
to: "truncated",
|
|
2925
|
+
reason: "data_table caption truncated to 150 chars",
|
|
2926
|
+
path: ctx.path
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
const rows = table.children.map(
|
|
2930
|
+
(row, rowIndex) => row.children.map((cell) => {
|
|
2931
|
+
if (rowIndex === 0) {
|
|
2932
|
+
return { type: "raw_text", text: toString5(cell) };
|
|
2933
|
+
}
|
|
2934
|
+
return cellToSlack(cell, ctx, false);
|
|
2935
|
+
})
|
|
2936
|
+
);
|
|
2937
|
+
return {
|
|
2938
|
+
type: "data_table",
|
|
2939
|
+
caption,
|
|
2940
|
+
rows,
|
|
2941
|
+
page_size: 5,
|
|
2942
|
+
row_header_column_index: 0
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
function cellToSlack(cell, ctx, header) {
|
|
2946
|
+
const plain = toString5(cell);
|
|
2947
|
+
const styled = cell.children.some(
|
|
2948
|
+
(c) => c.type === "strong" || c.type === "emphasis" || c.type === "delete" || c.type === "link" || c.type === "inlineCode"
|
|
2949
|
+
);
|
|
2950
|
+
if (!styled) {
|
|
2951
|
+
if (!header && /^-?\d+(\.\d+)?$/.test(plain.trim())) {
|
|
2952
|
+
const value = Number(plain.trim());
|
|
2953
|
+
return { type: "raw_number", value, text: plain.trim() };
|
|
2954
|
+
}
|
|
2955
|
+
return { type: "raw_text", text: plain };
|
|
2956
|
+
}
|
|
2957
|
+
const inline = emitPhrasing(cell.children, ctx);
|
|
2958
|
+
const rich = {
|
|
2959
|
+
type: "rich_text",
|
|
2960
|
+
elements: [
|
|
2961
|
+
{
|
|
2962
|
+
type: "rich_text_section",
|
|
2963
|
+
elements: inline.elements.length > 0 ? inline.elements : [{ type: "text", text: plain }]
|
|
2964
|
+
}
|
|
2965
|
+
]
|
|
2966
|
+
};
|
|
2967
|
+
return rich;
|
|
2968
|
+
}
|
|
2969
|
+
function columnSettings(table) {
|
|
2970
|
+
const align = table.align ?? [];
|
|
2971
|
+
const colCount = table.children[0]?.children.length ?? 0;
|
|
2972
|
+
const settings = [];
|
|
2973
|
+
for (let i = 0; i < colCount; i += 1) {
|
|
2974
|
+
const a = align[i];
|
|
2975
|
+
const long = table.children.some(
|
|
2976
|
+
(row) => toString5(row.children[i] ?? { type: "text", value: "" }).length > TABLE_CELL_WRAP_THRESHOLD
|
|
2977
|
+
);
|
|
2978
|
+
const alignVal = a === "left" || a === "center" || a === "right" ? a : void 0;
|
|
2979
|
+
if (alignVal === void 0 && !long) {
|
|
2980
|
+
settings.push(null);
|
|
2981
|
+
} else {
|
|
2982
|
+
settings.push({
|
|
2983
|
+
align: alignVal,
|
|
2984
|
+
is_wrapped: long ? true : void 0
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
return settings;
|
|
2989
|
+
}
|
|
2990
|
+
function countEmittedCellChars(table, ctx) {
|
|
2991
|
+
const probe = ctx.withDegradations(new RecordingDegradationCollector());
|
|
2992
|
+
let n = 0;
|
|
2993
|
+
for (const [rowIndex, row] of table.children.entries()) {
|
|
2994
|
+
for (const cell of row.children) {
|
|
2995
|
+
n += slackCellCharCount(cellToSlack(cell, probe, rowIndex === 0));
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
return n;
|
|
2999
|
+
}
|
|
3000
|
+
function slackCellCharCount(cell) {
|
|
3001
|
+
if (cell.type === "raw_text" || cell.type === "raw_number") {
|
|
3002
|
+
return cell.text.length;
|
|
3003
|
+
}
|
|
3004
|
+
return JSON.stringify(cell).length;
|
|
3005
|
+
}
|
|
3006
|
+
function truncateRows(table, maxRows) {
|
|
3007
|
+
return {
|
|
3008
|
+
...table,
|
|
3009
|
+
children: table.children.slice(0, maxRows)
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
function toPipeMarkdown(table) {
|
|
3013
|
+
const rows = table.children.map(
|
|
3014
|
+
(row) => row.children.map((c) => toString5(c).replace(/\|/g, "\\|"))
|
|
3015
|
+
);
|
|
3016
|
+
if (rows.length === 0) {
|
|
3017
|
+
return "";
|
|
3018
|
+
}
|
|
3019
|
+
const header = rows[0] ?? [];
|
|
3020
|
+
const align = table.align ?? [];
|
|
3021
|
+
const sep = header.map((_, i) => {
|
|
3022
|
+
const a = align[i];
|
|
3023
|
+
if (a === "center") return ":---:";
|
|
3024
|
+
if (a === "right") return "---:";
|
|
3025
|
+
if (a === "left") return ":---";
|
|
3026
|
+
return "---";
|
|
3027
|
+
});
|
|
3028
|
+
const lines = [
|
|
3029
|
+
`| ${header.join(" | ")} |`,
|
|
3030
|
+
`| ${sep.join(" | ")} |`,
|
|
3031
|
+
...rows.slice(1).map((r) => `| ${r.join(" | ")} |`)
|
|
3032
|
+
];
|
|
3033
|
+
return lines.join("\n");
|
|
3034
|
+
}
|
|
3035
|
+
function asciiTableBlock(table) {
|
|
3036
|
+
return preformattedRichText(toPipeMarkdown(table), void 0);
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
// src/adapters/task-list-adapter.ts
|
|
3040
|
+
import { toString as toString6 } from "mdast-util-to-string";
|
|
3041
|
+
var TaskListAdapter = class {
|
|
3042
|
+
match(node, _ctx) {
|
|
3043
|
+
return node.type === "list" && isTaskList(node);
|
|
3044
|
+
}
|
|
3045
|
+
plan(node, _ctx) {
|
|
3046
|
+
const list = node;
|
|
3047
|
+
const md = serializeTaskList(list);
|
|
3048
|
+
return [
|
|
3049
|
+
{
|
|
3050
|
+
requires: ["markdownBlock"],
|
|
3051
|
+
budget: { blocks: 1, markdownChars: md.length },
|
|
3052
|
+
emit: () => {
|
|
3053
|
+
const block = { type: "markdown", text: md };
|
|
3054
|
+
return [block];
|
|
3055
|
+
}
|
|
3056
|
+
},
|
|
3057
|
+
{
|
|
3058
|
+
budget: { blocks: 1 },
|
|
3059
|
+
emit: (ctx) => {
|
|
3060
|
+
ctx.degradations.add({
|
|
3061
|
+
nodeType: "list",
|
|
3062
|
+
from: "markdown.task_list",
|
|
3063
|
+
to: "rich_text_list.unicode_checkbox",
|
|
3064
|
+
reason: "markdownBlock unavailable or budget exhausted",
|
|
3065
|
+
path: ctx.path
|
|
3066
|
+
});
|
|
3067
|
+
return [unicodeTaskList(list, ctx)];
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
];
|
|
3071
|
+
}
|
|
3072
|
+
};
|
|
3073
|
+
function serializeTaskList(list, indent = 0) {
|
|
3074
|
+
const pad = " ".repeat(indent);
|
|
3075
|
+
const lines = [];
|
|
3076
|
+
for (const item of list.children) {
|
|
3077
|
+
const checked = item.checked === true;
|
|
3078
|
+
const box = checked ? "[x]" : "[ ]";
|
|
3079
|
+
const text = itemText(item);
|
|
3080
|
+
lines.push(`${pad}- ${box} ${text}`);
|
|
3081
|
+
for (const child of item.children) {
|
|
3082
|
+
if (child.type === "list") {
|
|
3083
|
+
lines.push(serializeTaskList(child, indent + 1));
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return lines.join("\n");
|
|
3088
|
+
}
|
|
3089
|
+
function itemText(item) {
|
|
3090
|
+
const parts = [];
|
|
3091
|
+
for (const child of item.children) {
|
|
3092
|
+
if (child.type === "paragraph") {
|
|
3093
|
+
parts.push(toString6(child));
|
|
3094
|
+
} else if (child.type !== "list") {
|
|
3095
|
+
parts.push(toString6(child));
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return parts.join(" ").trim();
|
|
3099
|
+
}
|
|
3100
|
+
function unicodeTaskList(list, ctx) {
|
|
3101
|
+
return {
|
|
3102
|
+
type: "rich_text",
|
|
3103
|
+
elements: emitUnicodeTaskTree(list, 0, ctx)
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
function emitUnicodeTaskTree(list, indent, ctx) {
|
|
3107
|
+
let effectiveIndent = indent;
|
|
3108
|
+
if (effectiveIndent > RICH_TEXT_LIST_INDENT_MAX) {
|
|
3109
|
+
ctx.degradations.add({
|
|
3110
|
+
nodeType: "list",
|
|
3111
|
+
from: `indent:${String(effectiveIndent)}`,
|
|
3112
|
+
to: `indent:${String(RICH_TEXT_LIST_INDENT_MAX)}`,
|
|
3113
|
+
reason: "Task list indent clamped at 6",
|
|
3114
|
+
path: ctx.path
|
|
3115
|
+
});
|
|
3116
|
+
effectiveIndent = RICH_TEXT_LIST_INDENT_MAX;
|
|
3117
|
+
}
|
|
3118
|
+
const sections = [];
|
|
3119
|
+
const nested = [];
|
|
3120
|
+
for (const item of list.children) {
|
|
3121
|
+
const prefix = item.checked === true ? "\u2611 " : item.checked === false ? "\u2610 " : "";
|
|
3122
|
+
sections.push({
|
|
3123
|
+
type: "rich_text_section",
|
|
3124
|
+
elements: [{ type: "text", text: `${prefix}${itemText(item)}` }]
|
|
3125
|
+
});
|
|
3126
|
+
for (const child of item.children) {
|
|
3127
|
+
if (child.type === "list") {
|
|
3128
|
+
nested.push(...emitUnicodeTaskTree(child, effectiveIndent + 1, ctx));
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
const primary = {
|
|
3133
|
+
type: "rich_text_list",
|
|
3134
|
+
style: "bullet",
|
|
3135
|
+
elements: sections.length > 0 ? sections : [{ type: "rich_text_section", elements: [{ type: "text", text: " " }] }],
|
|
3136
|
+
indent: effectiveIndent
|
|
3137
|
+
};
|
|
3138
|
+
return [primary, ...nested];
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// src/adapters/thematic-break-adapter.ts
|
|
3142
|
+
var ThematicBreakAdapter = class {
|
|
3143
|
+
match(node, _ctx) {
|
|
3144
|
+
return node.type === "thematicBreak";
|
|
3145
|
+
}
|
|
3146
|
+
plan(_node, _ctx) {
|
|
3147
|
+
return [
|
|
3148
|
+
{
|
|
3149
|
+
budget: { blocks: 1 },
|
|
3150
|
+
emit: (_ctx2) => [{ type: "divider" }]
|
|
3151
|
+
}
|
|
3152
|
+
];
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
|
|
3156
|
+
// src/converter/create-converter.ts
|
|
3157
|
+
function createConverter(options = {}) {
|
|
3158
|
+
const parser = options.parser ?? new RemarkMarkdownParser();
|
|
3159
|
+
const plainText = options.plainText ?? new DeterministicPlainTextRenderer();
|
|
3160
|
+
const pieParser = new MermaidPieParser();
|
|
3161
|
+
const xyParser = new MermaidXyChartParser();
|
|
3162
|
+
const builtIns = [
|
|
3163
|
+
new MermaidFenceAdapter({ pieParser, xyParser }),
|
|
3164
|
+
new MathBlockAdapter(),
|
|
3165
|
+
new TaskListAdapter(),
|
|
3166
|
+
new ListAdapter(),
|
|
3167
|
+
new TableAdapter(),
|
|
3168
|
+
new HeadingAdapter(),
|
|
3169
|
+
new ParagraphAdapter(),
|
|
3170
|
+
new BlockquoteAdapter(),
|
|
3171
|
+
new CodeFenceAdapter(),
|
|
3172
|
+
new ThematicBreakAdapter(),
|
|
3173
|
+
new HtmlBlockAdapter(),
|
|
3174
|
+
new SkipAdapter()
|
|
3175
|
+
];
|
|
3176
|
+
const registry = new AdapterRegistry([...options.adapters ?? [], ...builtIns]);
|
|
3177
|
+
const assembler = new MessageAssembler({ plainText });
|
|
3178
|
+
const defaultOptions = {
|
|
3179
|
+
capabilities: options.capabilities,
|
|
3180
|
+
capabilityOverrides: options.capabilityOverrides,
|
|
3181
|
+
overflow: options.overflow ?? OverflowMode.Degrade,
|
|
3182
|
+
strategy: options.strategy,
|
|
3183
|
+
renderers: options.renderers ?? [],
|
|
3184
|
+
uploader: options.uploader,
|
|
3185
|
+
enableMath: options.enableMath,
|
|
3186
|
+
enableFrontmatter: options.enableFrontmatter,
|
|
3187
|
+
mentionResolvers: options.mentionResolvers
|
|
3188
|
+
};
|
|
3189
|
+
return new SlackMarkConverter({
|
|
3190
|
+
parser,
|
|
3191
|
+
registry,
|
|
3192
|
+
assembler,
|
|
3193
|
+
plainText,
|
|
3194
|
+
defaultOptions
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
function convert(markdown, options) {
|
|
3198
|
+
return createConverter(options).convert(markdown, options);
|
|
3199
|
+
}
|
|
3200
|
+
function convertToMessages(markdown, options) {
|
|
3201
|
+
return createConverter(options).convertToMessages(markdown, options);
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/validate/validate-blocks.ts
|
|
3205
|
+
function validateBlocks(blocks) {
|
|
3206
|
+
const issues = [];
|
|
3207
|
+
if (blocks.length > MAX_BLOCKS_PER_MESSAGE) {
|
|
3208
|
+
issues.push({
|
|
3209
|
+
path: "$",
|
|
3210
|
+
message: `Too many blocks: ${String(blocks.length)} > ${String(MAX_BLOCKS_PER_MESSAGE)}`
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
let markdownChars = 0;
|
|
3214
|
+
let tableChars = 0;
|
|
3215
|
+
let dataTableChars = 0;
|
|
3216
|
+
let charts = 0;
|
|
3217
|
+
blocks.forEach((block, i) => {
|
|
3218
|
+
const path = `blocks[${String(i)}]`;
|
|
3219
|
+
switch (block.type) {
|
|
3220
|
+
case "header":
|
|
3221
|
+
if (block.text.text.length > HEADER_TEXT_MAX_CHARS) {
|
|
3222
|
+
issues.push({ path, message: "header text > 150" });
|
|
3223
|
+
}
|
|
3224
|
+
if (block.level !== void 0 && (block.level < 1 || block.level > 4)) {
|
|
3225
|
+
issues.push({ path, message: "header level out of 1\u20134" });
|
|
3226
|
+
}
|
|
3227
|
+
break;
|
|
3228
|
+
case "section":
|
|
3229
|
+
if (block.text !== void 0 && block.text.text.length > SECTION_TEXT_MAX_CHARS) {
|
|
3230
|
+
issues.push({ path, message: "section text > 3000" });
|
|
3231
|
+
}
|
|
3232
|
+
break;
|
|
3233
|
+
case "image":
|
|
3234
|
+
if (block.alt_text.length > IMAGE_ALT_MAX_CHARS) {
|
|
3235
|
+
issues.push({ path, message: "image alt > 2000" });
|
|
3236
|
+
}
|
|
3237
|
+
if (block.image_url !== void 0 && block.image_url.length > IMAGE_URL_MAX_CHARS) {
|
|
3238
|
+
issues.push({ path, message: "image_url > 3000" });
|
|
3239
|
+
}
|
|
3240
|
+
break;
|
|
3241
|
+
case "context":
|
|
3242
|
+
if (block.elements.length > CONTEXT_ELEMENTS_MAX) {
|
|
3243
|
+
issues.push({ path, message: "context elements > 10" });
|
|
3244
|
+
}
|
|
3245
|
+
break;
|
|
3246
|
+
case "markdown":
|
|
3247
|
+
markdownChars += block.text.length;
|
|
3248
|
+
break;
|
|
3249
|
+
case "table": {
|
|
3250
|
+
if (block.rows.length > TABLE_MAX_ROWS) {
|
|
3251
|
+
issues.push({ path, message: "table rows > 100" });
|
|
3252
|
+
}
|
|
3253
|
+
for (const row of block.rows) {
|
|
3254
|
+
if (row.length > TABLE_MAX_COLS) {
|
|
3255
|
+
issues.push({ path, message: "table cols > 20" });
|
|
3256
|
+
}
|
|
3257
|
+
for (const cell of row) {
|
|
3258
|
+
tableChars += cellCharCount(cell);
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
break;
|
|
3262
|
+
}
|
|
3263
|
+
case "data_table": {
|
|
3264
|
+
if (block.rows.length > DATA_TABLE_MAX_DATA_ROWS + 1) {
|
|
3265
|
+
issues.push({ path, message: "data_table rows > 201" });
|
|
3266
|
+
}
|
|
3267
|
+
for (const row of block.rows) {
|
|
3268
|
+
if (row.length > TABLE_MAX_COLS) {
|
|
3269
|
+
issues.push({ path, message: "data_table cols > 20" });
|
|
3270
|
+
}
|
|
3271
|
+
for (const cell of row) {
|
|
3272
|
+
dataTableChars += cellCharCount(cell);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
break;
|
|
3276
|
+
}
|
|
3277
|
+
case "data_visualization":
|
|
3278
|
+
charts += 1;
|
|
3279
|
+
if (block.title.length > CHART_TITLE_MAX_CHARS) {
|
|
3280
|
+
issues.push({ path, message: "chart title > 50" });
|
|
3281
|
+
}
|
|
3282
|
+
validateChart(block, path, issues);
|
|
3283
|
+
break;
|
|
3284
|
+
case "container":
|
|
3285
|
+
if (block.child_blocks.length > CONTAINER_CHILDREN_MAX) {
|
|
3286
|
+
issues.push({ path, message: "container children > 10" });
|
|
3287
|
+
}
|
|
3288
|
+
if (block.title !== void 0 && block.title.text.length > CONTAINER_TITLE_MAX_CHARS) {
|
|
3289
|
+
issues.push({ path, message: "container title > 150" });
|
|
3290
|
+
}
|
|
3291
|
+
break;
|
|
3292
|
+
case "divider":
|
|
3293
|
+
case "rich_text":
|
|
3294
|
+
break;
|
|
3295
|
+
default: {
|
|
3296
|
+
const exhaustive = block;
|
|
3297
|
+
void exhaustive;
|
|
3298
|
+
break;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
if (markdownChars > MARKDOWN_CUMULATIVE_MAX_CHARS) {
|
|
3303
|
+
issues.push({
|
|
3304
|
+
path: "$",
|
|
3305
|
+
message: `markdown cumulative ${String(markdownChars)} > 12000`
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
if (tableChars > TABLE_CELL_CHARS_MAX) {
|
|
3309
|
+
issues.push({
|
|
3310
|
+
path: "$",
|
|
3311
|
+
message: `table chars ${String(tableChars)} > 10000`
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
if (dataTableChars > DATA_TABLE_CELL_CHARS_MAX) {
|
|
3315
|
+
issues.push({
|
|
3316
|
+
path: "$",
|
|
3317
|
+
message: `data_table chars ${String(dataTableChars)} > 20000`
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
if (charts > DATA_VISUALIZATION_MAX_PER_MESSAGE) {
|
|
3321
|
+
issues.push({
|
|
3322
|
+
path: "$",
|
|
3323
|
+
message: `charts ${String(charts)} > ${String(DATA_VISUALIZATION_MAX_PER_MESSAGE)}`
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
if (issues.length > 0) {
|
|
3327
|
+
const detail = issues.map((x) => `${x.path}: ${x.message}`).join("; ");
|
|
3328
|
+
throw new Error(`validateBlocks failed: ${detail}`);
|
|
3329
|
+
}
|
|
3330
|
+
return blocks;
|
|
3331
|
+
}
|
|
3332
|
+
function cellCharCount(cell) {
|
|
3333
|
+
if (cell.type === "raw_text") {
|
|
3334
|
+
return cell.text.length;
|
|
3335
|
+
}
|
|
3336
|
+
if (cell.type === "raw_number") {
|
|
3337
|
+
return cell.text.length;
|
|
3338
|
+
}
|
|
3339
|
+
return JSON.stringify(cell).length;
|
|
3340
|
+
}
|
|
3341
|
+
function validateChart(block, path, issues) {
|
|
3342
|
+
const chart = block.chart;
|
|
3343
|
+
if (chart.type === "pie") {
|
|
3344
|
+
if (chart.segments.length < 1 || chart.segments.length > CHART_SEGMENTS_MAX) {
|
|
3345
|
+
issues.push({ path, message: "pie segments out of 1\u201312" });
|
|
3346
|
+
}
|
|
3347
|
+
for (const s of chart.segments) {
|
|
3348
|
+
if (s.label.length > CHART_LABEL_MAX_CHARS) {
|
|
3349
|
+
issues.push({ path, message: "pie label > 20" });
|
|
3350
|
+
}
|
|
3351
|
+
if (!(s.value > 0)) {
|
|
3352
|
+
issues.push({ path, message: "pie value must be > 0" });
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
if (chart.series.length < 1 || chart.series.length > CHART_SERIES_MAX) {
|
|
3358
|
+
issues.push({ path, message: "series out of 1\u201312" });
|
|
3359
|
+
}
|
|
3360
|
+
if (chart.axis_config.categories.length > CHART_POINTS_MAX) {
|
|
3361
|
+
issues.push({ path, message: "categories > 20" });
|
|
3362
|
+
}
|
|
3363
|
+
for (const s of chart.series) {
|
|
3364
|
+
if (s.name.length > CHART_LABEL_MAX_CHARS) {
|
|
3365
|
+
issues.push({ path, message: "series name > 20" });
|
|
3366
|
+
}
|
|
3367
|
+
if (s.data.length < 1 || s.data.length > CHART_POINTS_MAX) {
|
|
3368
|
+
issues.push({ path, message: "points out of 1\u201320" });
|
|
3369
|
+
}
|
|
3370
|
+
for (const p of s.data) {
|
|
3371
|
+
if (p.label.length > CHART_LABEL_MAX_CHARS) {
|
|
3372
|
+
issues.push({ path, message: "point label > 20" });
|
|
3373
|
+
}
|
|
3374
|
+
if (!chart.axis_config.categories.includes(p.label)) {
|
|
3375
|
+
issues.push({ path, message: `point label ${p.label} missing from categories` });
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
export {
|
|
3381
|
+
AdapterRegistry,
|
|
3382
|
+
CAPABILITY_PRESETS,
|
|
3383
|
+
CapabilityPresetName,
|
|
3384
|
+
ConfigurationError,
|
|
3385
|
+
ConversionContext,
|
|
3386
|
+
ConversionStrategy as ConversionStrategies,
|
|
3387
|
+
DeterministicPlainTextRenderer,
|
|
3388
|
+
MermaidPieParser,
|
|
3389
|
+
MermaidXyChartParser,
|
|
3390
|
+
MessageAssembler,
|
|
3391
|
+
OverflowMode as OverflowModes,
|
|
3392
|
+
RecordingDegradationCollector,
|
|
3393
|
+
RemarkMarkdownParser,
|
|
3394
|
+
SlackMarkConverter,
|
|
3395
|
+
StandardBudgetLedger,
|
|
3396
|
+
convert,
|
|
3397
|
+
convertToMessages,
|
|
3398
|
+
createConverter,
|
|
3399
|
+
resolveCapabilities,
|
|
3400
|
+
validateBlocks
|
|
3401
|
+
};
|
|
3402
|
+
//# sourceMappingURL=index.js.map
|