@portabletext/markdown 1.5.0 → 2.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/README.md +67 -9
- package/dist/index.d.ts +87 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1116 -280
- package/dist/index.js.map +1 -1
- package/package.json +10 -12
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { compileSchema, defineSchema, isSpan, isTextBlock, isTypedObject } from "@portabletext/schema";
|
|
2
|
-
import { buildMarksTree, isPortableTextBlock, isPortableTextListItemBlock, isPortableTextToolkitSpan, isPortableTextToolkitTextNode, spanToPlainText } from "@portabletext/toolkit";
|
|
2
|
+
import { buildMarksTree, isPortableTextBlock, isPortableTextListItemBlock, isPortableTextSpan, isPortableTextToolkitSpan, isPortableTextToolkitTextNode, spanToPlainText } from "@portabletext/toolkit";
|
|
3
|
+
import LinkifyIt from "linkify-it";
|
|
3
4
|
import { alert } from "@mdit/plugin-alert";
|
|
4
5
|
import markdownit from "markdown-it";
|
|
5
6
|
function defaultKeyGenerator() {
|
|
@@ -91,27 +92,431 @@ function buildListIndexMap(blocks) {
|
|
|
91
92
|
listDepthMap
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
/**
|
|
96
|
+
* The CommonMark ASCII punctuation set. Only these characters can be
|
|
97
|
+
* backslash-escaped into a literal without changing the parsed text.
|
|
98
|
+
*/
|
|
99
|
+
const ASCII_PUNCTUATION = /[!-/:-@[-`{-~]/, ENTITY_REFERENCE = /&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#[xX][0-9a-fA-F]+);/g, BACKSLASH_BEFORE_PUNCTUATION = RegExp(`\\\\(?=${ASCII_PUNCTUATION.source})`, "g"), TILDE_RUN = /~{2,}/g, HTML_LIKE_ANGLE_BRACKET = /<(?=[a-zA-Z/!?])/g, BRACKET_BEFORE_LINK_OPEN = /\](?=[([])/g, UNICODE_PUNCTUATION_OR_SYMBOL = /^(?:\p{P}|\p{S})$/u, linkify = new LinkifyIt();
|
|
100
|
+
/**
|
|
101
|
+
* Plans the escaped replacement for every plain-text leaf a block's children
|
|
102
|
+
* will produce, in the exact left-to-right order `renderText` visits them
|
|
103
|
+
* (mirroring `buildMarksTree`'s own `text.split('\n')` leaf splitting).
|
|
104
|
+
*
|
|
105
|
+
* Escaping runs ahead of rendering, over the flat span sequence: some
|
|
106
|
+
* hazards only exist across a leaf boundary (an ordered-list marker, a
|
|
107
|
+
* ref-def label, an emphasis run) because an annotation or decorator mark
|
|
108
|
+
* that introduces no markup of its own splices its children in seamlessly.
|
|
109
|
+
* The plan works line by line (a block's children joined into text, split
|
|
110
|
+
* at hard breaks) rather than leaf by leaf: each line's leaves are joined
|
|
111
|
+
* into one string first, opaque children (inline objects, or leaves a
|
|
112
|
+
* custom renderer will replace) masked with a sentinel that can't match any
|
|
113
|
+
* hazard, and every hazard - inline and line-start alike - is detected once
|
|
114
|
+
* against that real, complete line, with true left/right context on both
|
|
115
|
+
* sides. Detected hazards become position-tracked edits against the line's
|
|
116
|
+
* raw text, which are then split back into each contributing leaf's own
|
|
117
|
+
* escaped text; only that composition step is leaf-scoped.
|
|
118
|
+
*
|
|
119
|
+
* A joined line's text that markdown-it's own linkify pass (bundled as
|
|
120
|
+
* `linkify-it`) would claim as a bare URL or email is masked from most
|
|
121
|
+
* edits: the linkify carve-out promises that substring round-trips
|
|
122
|
+
* byte-identical, gaining only a link mark, so escaping inside it would
|
|
123
|
+
* corrupt text linkify is about to claim as a link's visible text. An
|
|
124
|
+
* entity-reference or backtick escape is never masked (see `computeLinkifyMask`
|
|
125
|
+
* for why), and a claim spliced across a decorator boundary is never masked
|
|
126
|
+
* in the first place.
|
|
127
|
+
*
|
|
128
|
+
* `isHeading` is set for ATX headings: only the first joined line sits
|
|
129
|
+
* inside the `# ` prefix an ATX heading can never be reparsed as a block
|
|
130
|
+
* construct within, so line-leading hazards are skipped there; a hard
|
|
131
|
+
* break's later lines are ordinary markdown lines and get the full
|
|
132
|
+
* line-start battery. That first line carries a line-*end* hazard of its
|
|
133
|
+
* own instead: a trailing `#`-run reads back as the heading's own optional
|
|
134
|
+
* closing sequence.
|
|
135
|
+
*
|
|
136
|
+
* `isListItem` is set when the block renders as list-item content: a
|
|
137
|
+
* `[ ] `/`[x] `/`[X] ` at the very start of the first joined line reads
|
|
138
|
+
* back as a GFM task-list checkbox, regardless of the list's own item type.
|
|
139
|
+
*
|
|
140
|
+
* `hardBreakOutputHasNewline` says whether the renderer's actual hard-break
|
|
141
|
+
* output contains a newline. A custom `hardBreak` can render to something
|
|
142
|
+
* with no newline of its own (eg `() => '<br />'`), in which case the
|
|
143
|
+
* leaves on either side of it land on the same rendered line, not two: a
|
|
144
|
+
* hard break like that can't be planned as a line boundary, so it's walled
|
|
145
|
+
* off as an opaque segment instead, the same protection an inline object's
|
|
146
|
+
* unknown rendered text already gets.
|
|
147
|
+
*/
|
|
148
|
+
function planLeafEscaping(children, markDefs, options) {
|
|
149
|
+
let linkMarkKeys = new Set(markDefs.filter((def) => def._type === "link").map((def) => def._key)), markDefKeys = new Set(markDefs.map((def) => def._key)), pieces = [];
|
|
150
|
+
for (let child of children) if (isPortableTextSpan(child)) {
|
|
151
|
+
let isLinkLabel = (child.marks ?? []).some((mark) => linkMarkKeys.has(mark)), markSignature = (child.marks ?? []).filter((mark) => !markDefKeys.has(mark)).sort().join(",");
|
|
152
|
+
child.text.split("\n").forEach((line, index) => {
|
|
153
|
+
index > 0 && pieces.push(options.hardBreakOutputHasNewline ? { kind: "hardBreak" } : { kind: "opaque" }), pieces.push({
|
|
154
|
+
kind: "text",
|
|
155
|
+
raw: line,
|
|
156
|
+
isLinkLabel,
|
|
157
|
+
markSignature
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
} else pieces.push({ kind: "opaque" });
|
|
161
|
+
let pieceOutputs = pieces.map(() => ""), lineIndex = 0, lineText = "", lineChars = [], lineIsLinkLabelChar = [], lineMarkSignature = [], flushLine = () => {
|
|
162
|
+
processLine({
|
|
163
|
+
text: lineText,
|
|
164
|
+
chars: lineChars,
|
|
165
|
+
isLinkLabelChar: lineIsLinkLabelChar,
|
|
166
|
+
markSignature: lineMarkSignature,
|
|
167
|
+
lineIndex,
|
|
168
|
+
isHeading: options.isHeading,
|
|
169
|
+
isListItem: options.isListItem,
|
|
170
|
+
pieceOutputs
|
|
171
|
+
}), lineIndex++, lineText = "", lineChars = [], lineIsLinkLabelChar = [], lineMarkSignature = [];
|
|
172
|
+
};
|
|
173
|
+
for (let pieceIndex = 0; pieceIndex < pieces.length; pieceIndex++) {
|
|
174
|
+
let piece = pieces[pieceIndex];
|
|
175
|
+
if (!piece || piece.kind === "hardBreak") {
|
|
176
|
+
flushLine();
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (piece.kind === "opaque") {
|
|
180
|
+
lineText += "\0", lineChars.push(null), lineIsLinkLabelChar.push(!1), lineMarkSignature.push("");
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
let prepared = piece.isLinkLabel ? escapeLinkLabelBrackets(piece.raw) : piece.raw;
|
|
184
|
+
for (let offset = 0; offset < prepared.length; offset++) lineText += prepared[offset], lineChars.push({
|
|
185
|
+
pieceIndex,
|
|
186
|
+
offset
|
|
187
|
+
}), lineIsLinkLabelChar.push(piece.isLinkLabel), lineMarkSignature.push(piece.markSignature);
|
|
98
188
|
}
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
flushLine();
|
|
190
|
+
let escaped = [];
|
|
191
|
+
return pieces.forEach((piece, index) => {
|
|
192
|
+
piece.kind === "text" && escaped.push(pieceOutputs[index] ?? "");
|
|
193
|
+
}), escaped;
|
|
194
|
+
}
|
|
195
|
+
function processLine(args) {
|
|
196
|
+
let { text, chars, isLinkLabelChar, markSignature, pieceOutputs } = args, linkifyMask = computeLinkifyMask(text, chars, isLinkLabelChar, markSignature);
|
|
197
|
+
applyEdits(text, chars, [...collectInlineEdits(text, isLinkLabelChar), ...collectLineStartEdits(text, args)].filter((edit) => edit.bypassLinkifyMask || !isMasked(edit, linkifyMask)), pieceOutputs);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Marks every character of this line that markdown-it's linkify pass would
|
|
201
|
+
* claim as part of a bare URL or email. Link-label and opaque characters
|
|
202
|
+
* are blanked out first: a link label's visible text sits inside `[...]`
|
|
203
|
+
* markup real linkify never reconsiders, and an opaque child's rendered
|
|
204
|
+
* text is unknown at plan time, so neither should join or seed a match.
|
|
205
|
+
*
|
|
206
|
+
* The probe only sees this line's raw, undecoded text, one hazard pass
|
|
207
|
+
* ahead of markdown-it's own pipeline: it runs linkify against inline
|
|
208
|
+
* tokenization and entity decoding, not before them. A claim survives only
|
|
209
|
+
* if it lies entirely inside one run of identical decorator marks: a
|
|
210
|
+
* decorator boundary crossing it splices that decorator's delimiters
|
|
211
|
+
* (`**`, `` ` ``, ...) into the middle of the range real linkify would see,
|
|
212
|
+
* which breaks the very claim being trusted. An annotation-only boundary
|
|
213
|
+
* (a link's own label text is already excluded above; any other
|
|
214
|
+
* annotation type falls back to rendering with no delimiters at all,
|
|
215
|
+
* same as an unregistered decorator) never splices, so it can't invalidate
|
|
216
|
+
* a claim either.
|
|
217
|
+
*/
|
|
218
|
+
function computeLinkifyMask(text, chars, isLinkLabelChar, markSignature) {
|
|
219
|
+
let mask = Array(text.length).fill(!1);
|
|
220
|
+
if (!/[.:@]/.test(text)) return mask;
|
|
221
|
+
let probe = "";
|
|
222
|
+
for (let index = 0; index < text.length; index++) probe += chars[index] === null || isLinkLabelChar[index] ? " " : text[index];
|
|
223
|
+
let matches = linkify.match(probe) ?? [];
|
|
224
|
+
for (let match of matches) {
|
|
225
|
+
if (match.schema === "") continue;
|
|
226
|
+
let signature = markSignature[match.index], staysWithinOneMarkRun = !0;
|
|
227
|
+
for (let index = match.index; index < match.lastIndex; index++) if (markSignature[index] !== signature) {
|
|
228
|
+
staysWithinOneMarkRun = !1;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
if (staysWithinOneMarkRun) for (let index = match.index; index < match.lastIndex; index++) mask[index] = !0;
|
|
232
|
+
}
|
|
233
|
+
return mask;
|
|
234
|
+
}
|
|
235
|
+
function isMasked(edit, mask) {
|
|
236
|
+
let end = edit.at + Math.max(edit.deleteCount, 1);
|
|
237
|
+
for (let index = edit.at; index < end; index++) if (mask[index]) return !0;
|
|
238
|
+
return !1;
|
|
239
|
+
}
|
|
240
|
+
/** Rewrites a line's raw text into each contributing leaf's escaped text by
|
|
241
|
+
* walking it once, left to right, applying at most one edit per position.
|
|
242
|
+
* Every hazard is keyed off its own trigger character - a backslash, a
|
|
243
|
+
* tilde, a backtick, an `&`, a `<`, a `*`/`_`, a `]`, or (line-start only,
|
|
244
|
+
* one hazard per line) a `#`, `>`, `[`, `-`/`+`/`*`, the `.`/`)` after an
|
|
245
|
+
* ordered-list marker's digits, `=`, 4 spaces, or a tab - and no two of
|
|
246
|
+
* those characters coincide at one position, so two edits can never target
|
|
247
|
+
* the same position. */
|
|
248
|
+
function applyEdits(text, chars, edits, pieceOutputs) {
|
|
249
|
+
let editsByPosition = /* @__PURE__ */ new Map();
|
|
250
|
+
for (let edit of edits) {
|
|
251
|
+
if (editsByPosition.has(edit.at)) throw Error(`Two hazard edits targeted the same position (${edit.at}); hazard trigger characters are assumed disjoint by construction.`);
|
|
252
|
+
editsByPosition.set(edit.at, edit);
|
|
253
|
+
}
|
|
254
|
+
let index = 0;
|
|
255
|
+
for (; index < text.length;) {
|
|
256
|
+
let edit = editsByPosition.get(index), owner = chars[index];
|
|
257
|
+
if (edit && (owner && (pieceOutputs[owner.pieceIndex] = (pieceOutputs[owner.pieceIndex] ?? "") + edit.insert), edit.deleteCount > 0)) {
|
|
258
|
+
index += edit.deleteCount;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
owner && (pieceOutputs[owner.pieceIndex] = (pieceOutputs[owner.pieceIndex] ?? "") + (text[index] ?? "")), index++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Hazards that can appear anywhere on a line: emphasis/strikethrough runs,
|
|
266
|
+
* a backtick, an entity reference, an HTML/autolink-shaped `<`, a literal
|
|
267
|
+
* backslash before punctuation, and a `]` immediately before `(`/`[`
|
|
268
|
+
* (which would otherwise read back as a link/image open).
|
|
269
|
+
*/
|
|
270
|
+
function collectInlineEdits(text, isLinkLabelChar) {
|
|
271
|
+
let edits = [];
|
|
272
|
+
for (let match of text.matchAll(BACKSLASH_BEFORE_PUNCTUATION)) {
|
|
273
|
+
let at = match.index ?? 0;
|
|
274
|
+
isLinkLabelChar[at] || edits.push({
|
|
275
|
+
at,
|
|
276
|
+
deleteCount: 0,
|
|
277
|
+
insert: "\\"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
for (let match of text.matchAll(TILDE_RUN)) {
|
|
281
|
+
let start = match.index ?? 0;
|
|
282
|
+
for (let index = start; index < start + match[0].length; index++) edits.push({
|
|
283
|
+
at: index,
|
|
284
|
+
deleteCount: 0,
|
|
285
|
+
insert: "\\"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
for (let index = 0; index < text.length; index++) text[index] === "`" && edits.push({
|
|
289
|
+
at: index,
|
|
290
|
+
deleteCount: 0,
|
|
291
|
+
insert: "\\",
|
|
292
|
+
bypassLinkifyMask: !0
|
|
293
|
+
});
|
|
294
|
+
for (let match of text.matchAll(ENTITY_REFERENCE)) edits.push({
|
|
295
|
+
at: match.index ?? 0,
|
|
296
|
+
deleteCount: 0,
|
|
297
|
+
insert: "\\",
|
|
298
|
+
bypassLinkifyMask: !0
|
|
299
|
+
});
|
|
300
|
+
for (let match of text.matchAll(HTML_LIKE_ANGLE_BRACKET)) edits.push({
|
|
301
|
+
at: match.index ?? 0,
|
|
302
|
+
deleteCount: 0,
|
|
303
|
+
insert: "\\"
|
|
304
|
+
});
|
|
305
|
+
edits.push(...collectEmphasisEdits(text));
|
|
306
|
+
for (let match of text.matchAll(BRACKET_BEFORE_LINK_OPEN)) {
|
|
307
|
+
let at = match.index ?? 0;
|
|
308
|
+
isLinkLabelChar[at] || edits.push({
|
|
309
|
+
at,
|
|
310
|
+
deleteCount: 0,
|
|
311
|
+
insert: "\\"
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return edits;
|
|
315
|
+
}
|
|
316
|
+
function isWhitespace(char) {
|
|
317
|
+
return char === void 0 || /\s/.test(char);
|
|
318
|
+
}
|
|
319
|
+
function isPunctuation(char) {
|
|
320
|
+
return char !== void 0 && UNICODE_PUNCTUATION_OR_SYMBOL.test(char);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The full code point sitting immediately before `index`: two UTF-16 code
|
|
324
|
+
* units for an astral character (eg an emoji) whose low surrogate lands at
|
|
325
|
+
* `index - 1`, one otherwise.
|
|
326
|
+
*/
|
|
327
|
+
function codePointBefore(text, index) {
|
|
328
|
+
if (!(index <= 0)) return index >= 2 && isLowSurrogate(text[index - 1]) && isHighSurrogate(text[index - 2]) ? text.slice(index - 2, index) : text[index - 1];
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The full code point sitting immediately at `index`: two UTF-16 code units
|
|
332
|
+
* for an astral character whose high surrogate lands at `index`, one
|
|
333
|
+
* otherwise.
|
|
334
|
+
*/
|
|
335
|
+
function codePointAt(text, index) {
|
|
336
|
+
if (!(index >= text.length)) return isHighSurrogate(text[index]) && isLowSurrogate(text[index + 1]) ? text.slice(index, index + 2) : text[index];
|
|
337
|
+
}
|
|
338
|
+
function isHighSurrogate(char) {
|
|
339
|
+
if (char === void 0) return !1;
|
|
340
|
+
let code = char.charCodeAt(0);
|
|
341
|
+
return code >= 55296 && code <= 56319;
|
|
342
|
+
}
|
|
343
|
+
function isLowSurrogate(char) {
|
|
344
|
+
if (char === void 0) return !1;
|
|
345
|
+
let code = char.charCodeAt(0);
|
|
346
|
+
return code >= 56320 && code <= 57343;
|
|
347
|
+
}
|
|
348
|
+
function isLeftFlanking(before, after) {
|
|
349
|
+
return isWhitespace(after) ? !1 : !isPunctuation(after) || isWhitespace(before) || isPunctuation(before);
|
|
350
|
+
}
|
|
351
|
+
function isRightFlanking(before, after) {
|
|
352
|
+
return isWhitespace(before) ? !1 : !isPunctuation(before) || isWhitespace(after) || isPunctuation(after);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Finds `*`/`_` runs CommonMark would treat as flanking delimiters, using
|
|
356
|
+
* each run's true neighbors on the joined line (the start/end of the line
|
|
357
|
+
* itself counts as whitespace, matching the spec's treatment of line
|
|
358
|
+
* boundaries).
|
|
359
|
+
*/
|
|
360
|
+
function collectEmphasisEdits(text) {
|
|
361
|
+
let edits = [], index = 0;
|
|
362
|
+
for (; index < text.length;) {
|
|
363
|
+
let char = text[index];
|
|
364
|
+
if (char !== "*" && char !== "_") {
|
|
365
|
+
index++;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
let end = index;
|
|
369
|
+
for (; end < text.length && text[end] === char;) end++;
|
|
370
|
+
let before = codePointBefore(text, index), after = codePointAt(text, end), leftFlanking = isLeftFlanking(before, after), rightFlanking = isRightFlanking(before, after), canOpen = char === "_" ? leftFlanking && (!rightFlanking || isPunctuation(before)) : leftFlanking, canClose = char === "_" ? rightFlanking && (!leftFlanking || isPunctuation(after)) : rightFlanking;
|
|
371
|
+
if (canOpen || canClose) for (let position = index; position < end; position++) edits.push({
|
|
372
|
+
at: position,
|
|
373
|
+
deleteCount: 0,
|
|
374
|
+
insert: "\\"
|
|
375
|
+
});
|
|
376
|
+
index = end;
|
|
377
|
+
}
|
|
378
|
+
return edits;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Hazards that only matter at the start (or, for a handful of whole-line
|
|
382
|
+
* constructs, the start *and* end) of a line: headings, blockquotes, list
|
|
383
|
+
* markers, ref-defs, setext underlines, thematic breaks, indented code, and
|
|
384
|
+
* a list item's own GFM task-checkbox prefix. A fence needs no branch of
|
|
385
|
+
* its own here: the inline backtick/tilde escaping every line already
|
|
386
|
+
* neutralizes the run a fence needs, so it can never open one on reparse.
|
|
387
|
+
* The remaining branches are mutually exclusive by construction
|
|
388
|
+
* (each targets a disjoint leading character) and return as soon as one
|
|
389
|
+
* matches, mirroring how CommonMark itself commits to one block-start
|
|
390
|
+
* interpretation per line; the checkbox branch above is the one exception,
|
|
391
|
+
* since a list item's checkbox prefix and, say, its heading marker are two
|
|
392
|
+
* independent hazards that can both apply to the same first line.
|
|
393
|
+
*/
|
|
394
|
+
function collectLineStartEdits(text, context) {
|
|
395
|
+
let edits = [], isFirstLine = context.lineIndex === 0, leadingSpaces = /^ {0,3}/.exec(text)?.[0].length ?? 0, rest = text.slice(leadingSpaces);
|
|
396
|
+
if (context.isListItem && isFirstLine && /^\[[ xX]\] /.test(rest) && edits.push({
|
|
397
|
+
at: leadingSpaces,
|
|
398
|
+
deleteCount: 0,
|
|
399
|
+
insert: "\\"
|
|
400
|
+
}), context.isHeading && isFirstLine) {
|
|
401
|
+
let closingSequence = /^(?:(.*[ \t]))?(#+[ \t]*)$/.exec(text);
|
|
402
|
+
return closingSequence && edits.push({
|
|
403
|
+
at: closingSequence[1]?.length ?? 0,
|
|
404
|
+
deleteCount: 0,
|
|
405
|
+
insert: "\\"
|
|
406
|
+
}), edits;
|
|
407
|
+
}
|
|
408
|
+
let orderedListMarker = /^ {0,3}(\d{1,9})([.)])(?=[ \t]|$)/.exec(text);
|
|
409
|
+
if (orderedListMarker) return edits.push({
|
|
410
|
+
at: orderedListMarker[0].length - 1,
|
|
411
|
+
deleteCount: 0,
|
|
412
|
+
insert: "\\"
|
|
413
|
+
}), edits;
|
|
414
|
+
if (/^#{1,6}(?:[ \t]|$)/.test(rest) || rest.startsWith(">") || /^\[[^\]\n]*\]:/.test(rest) || /^[-+*](?:[ \t]|$)/.test(rest) || /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/.test(text) || /^ {0,3}=+[ \t]*$/.test(text) || /^ {0,3}-+[ \t]*$/.test(text)) return edits.push({
|
|
415
|
+
at: leadingSpaces,
|
|
416
|
+
deleteCount: 0,
|
|
417
|
+
insert: "\\"
|
|
418
|
+
}), edits;
|
|
419
|
+
if (/^ {4}/.test(text)) return edits.push({
|
|
420
|
+
at: 0,
|
|
421
|
+
deleteCount: 1,
|
|
422
|
+
insert: " "
|
|
423
|
+
}), edits;
|
|
424
|
+
let tabIndent = /^ {0,3}\t/.exec(text);
|
|
425
|
+
return tabIndent && edits.push({
|
|
426
|
+
at: tabIndent[0].length - 1,
|
|
427
|
+
deleteCount: 1,
|
|
428
|
+
insert: "	"
|
|
429
|
+
}), edits;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Text rendered inside a link label needs every `[`, `]` and `\` escaped
|
|
433
|
+
* unconditionally, on top of the general-purpose hazard escaping every
|
|
434
|
+
* line gets: a link label must stay bracket-balanced, and any literal
|
|
435
|
+
* backslash in it needs protecting regardless of what follows (unlike
|
|
436
|
+
* plain text, where only a backslash immediately before punctuation is a
|
|
437
|
+
* hazard).
|
|
438
|
+
*/
|
|
439
|
+
function escapeLinkLabelBrackets(text) {
|
|
440
|
+
return text.replace(/[[\]\\]/g, (char) => `\\${char}`);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Blocks currently known to be a list item's first content block: it shares
|
|
444
|
+
* its first line with the list marker (and, for a task item, its GFM
|
|
445
|
+
* checkbox), which changes how `renderBlock` plans line-start hazard
|
|
446
|
+
* escaping. Internal to this package so the signal never reaches the
|
|
447
|
+
* public `Serializable`/`RenderNode` types a custom renderer's `.d.ts`
|
|
448
|
+
* would otherwise expose it through.
|
|
449
|
+
*
|
|
450
|
+
* A block is marked right before rendering it; the `renderNode` call that
|
|
451
|
+
* dispatches to `renderBlock` consumes the membership on the way past so a
|
|
452
|
+
* later, unrelated render of the same object (still possible - `renderNode`
|
|
453
|
+
* accepts any `TypedObject`) doesn't inherit a stale claim.
|
|
454
|
+
*/
|
|
455
|
+
const listItemFirstBlocks = /* @__PURE__ */ new WeakSet();
|
|
456
|
+
function markListItemFirstBlock(block) {
|
|
457
|
+
listItemFirstBlocks.add(block);
|
|
458
|
+
}
|
|
459
|
+
function consumeListItemFirstBlock(block) {
|
|
460
|
+
let isListItemFirstBlock = listItemFirstBlocks.has(block);
|
|
461
|
+
return listItemFirstBlocks.delete(block), isListItemFirstBlock;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* ATX headings are single-line, inline-only leaf blocks: an ATX heading's
|
|
465
|
+
* first line sits inside its `# ` prefix and can never be reparsed as a
|
|
466
|
+
* block construct, so line-leading hazards never apply there. A hard
|
|
467
|
+
* break's later lines are ordinary markdown lines outside that prefix and
|
|
468
|
+
* get the full line-start battery, same as any other block's continuation.
|
|
469
|
+
*/
|
|
470
|
+
const HEADING_STYLES = /* @__PURE__ */ new Set([
|
|
471
|
+
"h1",
|
|
472
|
+
"h2",
|
|
473
|
+
"h3",
|
|
474
|
+
"h4",
|
|
475
|
+
"h5",
|
|
476
|
+
"h6"
|
|
477
|
+
]), createRenderNode = (renderers, listIndexMap, listDepthMap) => {
|
|
478
|
+
let escapedTextByNode = /* @__PURE__ */ new WeakMap(), hardBreakOutputHasNewline = renderers.hardBreak().includes("\n");
|
|
479
|
+
function renderBlockChildren(node, isHeading, isListItem = !1) {
|
|
480
|
+
let chunks = planLeafEscaping(node.children ?? [], node.markDefs ?? [], {
|
|
481
|
+
isHeading,
|
|
482
|
+
isListItem,
|
|
483
|
+
hardBreakOutputHasNewline
|
|
484
|
+
}), tree = buildMarksTree(node);
|
|
485
|
+
return assignEscapedText(tree, chunks), tree.map((child, i) => renderNode({
|
|
101
486
|
node: child,
|
|
102
487
|
isInline: !0,
|
|
103
488
|
index: i,
|
|
104
489
|
renderNode
|
|
105
490
|
})).join("");
|
|
491
|
+
}
|
|
492
|
+
function assignEscapedText(nodes, chunks) {
|
|
493
|
+
let pointer = 0, visit = (node) => {
|
|
494
|
+
if (isPortableTextToolkitTextNode(node)) {
|
|
495
|
+
if (node.text !== "\n") {
|
|
496
|
+
let escaped = chunks[pointer];
|
|
497
|
+
escaped !== void 0 && escapedTextByNode.set(node, escaped), pointer++;
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
isPortableTextToolkitSpan(node) && node.children.forEach(visit);
|
|
502
|
+
};
|
|
503
|
+
nodes.forEach(visit);
|
|
504
|
+
}
|
|
505
|
+
function renderNode(options) {
|
|
506
|
+
let { node, index, isInline } = options;
|
|
507
|
+
return isPortableTextListItemBlock(node) ? renderListItem(node, index) : isPortableTextToolkitSpan(node) ? renderSpan(node) : isPortableTextBlock(node) ? renderBlock(node, index, isInline, consumeListItemFirstBlock(node)) : isPortableTextToolkitTextNode(node) ? renderText(node) : renderCustomBlock(node, index, isInline);
|
|
508
|
+
}
|
|
509
|
+
function renderListItem(node, index) {
|
|
510
|
+
let renderer = renderers.listItem, itemHandler = (typeof renderer == "function" ? renderer : renderer[node.listItem]) || renderers.unknownListItem, children;
|
|
106
511
|
if (node.style && node.style !== "normal") {
|
|
107
512
|
let { listItem: _listItem, ...blockNode } = node;
|
|
108
|
-
children = renderNode({
|
|
513
|
+
markListItemFirstBlock(blockNode), children = renderNode({
|
|
109
514
|
node: blockNode,
|
|
110
515
|
index,
|
|
111
516
|
isInline: !1,
|
|
112
517
|
renderNode
|
|
113
518
|
}), children = children.replace(/\n+$/, "");
|
|
114
|
-
}
|
|
519
|
+
} else children = renderBlockChildren(node, !1, !0);
|
|
115
520
|
return itemHandler({
|
|
116
521
|
value: node,
|
|
117
522
|
index,
|
|
@@ -138,21 +543,18 @@ const createRenderNode = (renderers, listIndexMap, listDepthMap) => {
|
|
|
138
543
|
children: children.join("")
|
|
139
544
|
});
|
|
140
545
|
}
|
|
141
|
-
function renderBlock(node, index, isInline) {
|
|
142
|
-
let
|
|
143
|
-
|
|
546
|
+
function renderBlock(node, index, isInline, isListItem) {
|
|
547
|
+
let style = node.style || "normal", children = renderBlockChildren(node, HEADING_STYLES.has(style), isListItem);
|
|
548
|
+
return ((typeof renderers.block == "function" ? renderers.block : renderers.block[style]) || renderers.unknownBlockStyle)({
|
|
144
549
|
index,
|
|
145
550
|
isInline,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return ((typeof renderers.block == "function" ? renderers.block : renderers.block[style]) || renderers.unknownBlockStyle)({
|
|
149
|
-
...props,
|
|
150
|
-
value: props.node,
|
|
551
|
+
children,
|
|
552
|
+
value: node,
|
|
151
553
|
renderNode
|
|
152
554
|
});
|
|
153
555
|
}
|
|
154
556
|
function renderText(node) {
|
|
155
|
-
return node.text === "\n" ? renderers.hardBreak() : node.text;
|
|
557
|
+
return node.text === "\n" ? renderers.hardBreak() : escapedTextByNode.get(node) ?? node.text;
|
|
156
558
|
}
|
|
157
559
|
function renderCustomBlock(value, index, isInline) {
|
|
158
560
|
return (renderers.types[value._type] ?? renderers.unknownType)({
|
|
@@ -163,26 +565,7 @@ const createRenderNode = (renderers, listIndexMap, listDepthMap) => {
|
|
|
163
565
|
});
|
|
164
566
|
}
|
|
165
567
|
return renderNode;
|
|
166
|
-
}
|
|
167
|
-
function serializeBlock(options) {
|
|
168
|
-
let { node, index, isInline, renderNode } = options, renderedChildren = buildMarksTree(node).map((child, i) => renderNode({
|
|
169
|
-
node: child,
|
|
170
|
-
isInline: !0,
|
|
171
|
-
index: i,
|
|
172
|
-
renderNode
|
|
173
|
-
}));
|
|
174
|
-
return {
|
|
175
|
-
_key: node._key || defaultKeyGenerator(),
|
|
176
|
-
children: renderedChildren.join(""),
|
|
177
|
-
index,
|
|
178
|
-
isInline,
|
|
179
|
-
node
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* @public
|
|
184
|
-
*/
|
|
185
|
-
const DefaultBlockSpacingRenderer = ({ current, next }) => isPortableTextListItemBlock(current) && isPortableTextListItemBlock(next) ? "\n" : isPortableTextBlock(current) && isPortableTextBlock(next) && current.style === "blockquote" && next.style === "blockquote" ? "\n>\n" : "\n\n", DefaultHardBreakRenderer = () => " \n", DefaultListItemRenderer = ({ children, value, listIndex, listDepth }) => {
|
|
568
|
+
}, DefaultBlockSpacingRenderer = ({ current, next }) => isPortableTextListItemBlock(current) && isPortableTextListItemBlock(next) ? "\n" : isPortableTextBlock(current) && isPortableTextBlock(next) && current.style === "blockquote" && next.style === "blockquote" ? "\n>\n" : "\n\n", DefaultHardBreakRenderer = () => " \n", DefaultListItemRenderer = ({ children, value, listIndex, listDepth }) => {
|
|
186
569
|
let listStyle = value.listItem || "bullet", depth = listDepth ?? (value.level || 1) - 1, indent = " ".repeat(depth);
|
|
187
570
|
return listStyle === "number" ? `${indent}${listIndex ?? 1}. ${children}` : listStyle === "task" ? `${indent}- ${"checked" in value && typeof value.checked == "boolean" && value.checked ? "[x]" : "[ ]"} ${children}` : `${indent}- ${children}`;
|
|
188
571
|
}, DefaultUnknownListItemRenderer = ({ children }) => `- ${children}\n`;
|
|
@@ -208,32 +591,39 @@ function escapeImageAndLinkTitle(text) {
|
|
|
208
591
|
* Escapes characters that have special meaning at the row level of a GFM
|
|
209
592
|
* table cell.
|
|
210
593
|
*
|
|
211
|
-
* A literal `|` ends the cell, so
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
594
|
+
* A literal `|` ends the cell, so a pipe preceded by an even number of
|
|
595
|
+
* backslashes (including zero) gets one more: paired backslashes cancel
|
|
596
|
+
* out to a literal backslash and leave the pipe live, so parity, not mere
|
|
597
|
+
* presence, decides whether it is already escaped. Newlines end the row,
|
|
598
|
+
* so they are replaced with `<br>` to keep the visible line break inside
|
|
599
|
+
* the cell.
|
|
216
600
|
*
|
|
217
|
-
* Backslashes are
|
|
218
|
-
* already in the rendered cell (such as `\[` and `\]` in
|
|
219
|
-
*
|
|
601
|
+
* Backslashes themselves are left alone here; only the parity check reads
|
|
602
|
+
* them, so escapes already in the rendered cell (such as `\[` and `\]` in
|
|
603
|
+
* link text) survive the pass untouched.
|
|
220
604
|
*/
|
|
221
605
|
function escapeTableCell(text) {
|
|
222
|
-
return text.replace(
|
|
606
|
+
return text.replace(/(\\*)\|/g, (match, backslashes) => backslashes.length % 2 == 0 ? `${backslashes}\\|` : match).replace(/\n/g, "<br>");
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* @public
|
|
610
|
+
*/
|
|
611
|
+
const DefaultEmRenderer = ({ children }) => `_${children}_`, DefaultStrongRenderer = ({ children }) => `**${children}**`, DefaultCodeRenderer = ({ text }) => wrapInCodeSpan(text);
|
|
612
|
+
function wrapInCodeSpan(text) {
|
|
613
|
+
let fence = "`".repeat(longestBacktickRun(text) + 1), touchesBacktick = text.startsWith("`") || text.endsWith("`"), wouldBeStripped = text.startsWith(" ") && text.endsWith(" ") && text.trim() !== "", padding = touchesBacktick || wouldBeStripped ? " " : "";
|
|
614
|
+
return `${fence}${padding}${text}${padding}${fence}`;
|
|
615
|
+
}
|
|
616
|
+
function longestBacktickRun(text) {
|
|
617
|
+
let longest = 0;
|
|
618
|
+
for (let run of text.match(/`+/g) ?? []) longest = Math.max(longest, run.length);
|
|
619
|
+
return longest;
|
|
223
620
|
}
|
|
224
621
|
/**
|
|
225
622
|
* @public
|
|
226
623
|
*/
|
|
227
|
-
const
|
|
624
|
+
const DefaultUnderlineRenderer = ({ children }) => `<u>${children}</u>`, DefaultStrikeThroughRenderer = ({ children }) => `~~${children}~~`, DefaultLinkRenderer = ({ children, value }) => {
|
|
228
625
|
let href = value?.href || "", title = value?.title || "";
|
|
229
|
-
|
|
230
|
-
if (/["'][^"']*[<>]|[<>][^<>]*["']/.test(href)) {
|
|
231
|
-
let encodedHref = href.replace(/["<>() ]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
232
|
-
return `[${escapeImageAndLinkText(children)}](${encodedHref})`;
|
|
233
|
-
}
|
|
234
|
-
return `[${escapeImageAndLinkText(children)}](${href}${title ? ` "${escapeImageAndLinkTitle(title)}"` : ""})`;
|
|
235
|
-
}
|
|
236
|
-
return children;
|
|
626
|
+
return uriLooksSafe(href) ? /["'][^"']*[<>]|[<>][^<>]*["']/.test(href) ? `[${children}](${href.replace(/["<>() ]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)})` : `[${children}](${href}${title ? ` "${escapeImageAndLinkTitle(title)}"` : ""})` : children;
|
|
237
627
|
};
|
|
238
628
|
function uriLooksSafe(uri) {
|
|
239
629
|
let url = (uri || "").trim(), first = url.charAt(0);
|
|
@@ -267,7 +657,7 @@ function isCodeShaped(value) {
|
|
|
267
657
|
* as absent instead of guarded.
|
|
268
658
|
*/
|
|
269
659
|
function normalizeLanguage(language) {
|
|
270
|
-
return typeof language != "string" || language.includes("\n") ? "" : language;
|
|
660
|
+
return typeof language != "string" || language.includes("\n") || language === "json:object" ? "" : language;
|
|
271
661
|
}
|
|
272
662
|
/**
|
|
273
663
|
* @public
|
|
@@ -393,7 +783,7 @@ const DefaultBlockquoteObjectRenderer = ({ value, renderNode }) => value.content
|
|
|
393
783
|
})).join("\n\n").split("\n").map((line) => line === "" ? ">" : `> ${line}`).join("\n"), DefaultListRenderer = ({ value, renderNode }) => {
|
|
394
784
|
let itemSeparator = value.items.some((item) => item.content.filter((block) => block._type !== "list").length > 1) ? "\n\n" : "\n";
|
|
395
785
|
return value.items.map((item, itemIndex) => {
|
|
396
|
-
let marker = getListMarker(value.kind, itemIndex, item.checked), indentWidth = value.kind === "task" ? 2 : marker.length, indent = " ".repeat(indentWidth), [first, ...rest] = item.content.map((block, blockIndex) => ({
|
|
786
|
+
let marker = getListMarker(value.kind, itemIndex, item.checked), indentWidth = value.kind === "task" ? 2 : marker.length, indent = " ".repeat(indentWidth), [first, ...rest] = item.content.map((block, blockIndex) => (blockIndex === 0 && isPortableTextBlock(block) && markListItemFirstBlock(block), {
|
|
397
787
|
isNestedList: block._type === "list",
|
|
398
788
|
text: renderNode({
|
|
399
789
|
node: block,
|
|
@@ -414,10 +804,7 @@ function getListMarker(kind, itemIndex, checked) {
|
|
|
414
804
|
/**
|
|
415
805
|
* @public
|
|
416
806
|
*/
|
|
417
|
-
const DefaultUnknownTypeRenderer = ({ value, isInline }) => {
|
|
418
|
-
let json = `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
|
|
419
|
-
return isInline ? `\n${json}\n` : json;
|
|
420
|
-
}, defaultRenderers = {
|
|
807
|
+
const DefaultUnknownTypeRenderer = ({ value, isInline }) => isInline ? `json:object${wrapInCodeSpan(JSON.stringify(value))}` : `\`\`\`json:object\n${JSON.stringify(value, null, 2)}\n\`\`\``, defaultRenderers = {
|
|
421
808
|
types: {
|
|
422
809
|
callout: DefaultCalloutRenderer,
|
|
423
810
|
code: DefaultCodeBlockRenderer,
|
|
@@ -611,7 +998,66 @@ const normalStyleDefinition = { name: "normal" }, h1StyleDefinition = { name: "h
|
|
|
611
998
|
defaultTableObjectDefinition
|
|
612
999
|
],
|
|
613
1000
|
inlineObjects: [defaultImageObjectDefinition]
|
|
614
|
-
}))
|
|
1001
|
+
})), degradationMessage = {
|
|
1002
|
+
"decorator-dropped": (decorator) => {
|
|
1003
|
+
switch (decorator) {
|
|
1004
|
+
case "code": return "Removed inline-code formatting, kept the text: the schema has no `code` decorator";
|
|
1005
|
+
case "strong": return "Removed bold formatting, kept the text: the schema has no `strong` decorator";
|
|
1006
|
+
case "em": return "Removed italic formatting, kept the text: the schema has no `em` decorator";
|
|
1007
|
+
case "strikeThrough": return "Removed strikethrough formatting, kept the text: the schema has no `strike-through` decorator";
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
"annotation-dropped": (cause) => cause === "missing-url" ? "Removed a link that has no URL, kept its text" : "Removed the link, kept its text: the schema has no `link` annotation",
|
|
1011
|
+
"style-fallback": (name) => /^h[1-6]$/.test(name) ? `\`${"#".repeat(Number(name.slice(1)))}\` heading became a normal paragraph: the schema has no \`${name}\` style` : name === "blockquote" ? "Blockquote became normal paragraphs: the schema has no `blockquote` style" : `Fell back to \`normal\` style: \`${name}\` not in schema`,
|
|
1012
|
+
"list-flattened": (kind) => `${kind === "number" ? "Numbered" : "Bullet"} list became plain paragraphs: the schema has no \`${kind}\` list`,
|
|
1013
|
+
"task-checkbox-stripped": (checked) => `Removed the \`${checked ? "[x]" : "[ ]"}\` checkbox, kept a plain list item: the schema has no \`task\` list`,
|
|
1014
|
+
"table-flattened": "Table became plain text blocks, rows and columns lost: the schema has no `table` block object",
|
|
1015
|
+
"code-block-to-text": (language) => language ? `\`${language}\` code block became plain text: the schema has no \`code\` block object` : "Code block became plain text: the schema has no `code` block object",
|
|
1016
|
+
"horizontal-rule-to-text": "Horizontal rule became the text `---`: the schema has no `horizontal-rule` block object",
|
|
1017
|
+
"html-block-to-text": "HTML block became plain text: the schema has no `html` block object",
|
|
1018
|
+
"inline-html-dropped": "Removed inline HTML tags, kept nothing: `html.inline` is `skip` (the default)",
|
|
1019
|
+
"image-block-to-inline": (cause) => cause === "table-cell" ? "The image became inline: a table cell can't hold a block-level `image`" : "The image became inline: the schema has no block-level `image`",
|
|
1020
|
+
"image-inline-to-block": "The image became its own block, splitting the paragraph: the schema has no inline `image`",
|
|
1021
|
+
"image-to-text": "Image became its markdown source as plain text: the schema has no `image` object",
|
|
1022
|
+
"callout-fallback": (calloutType, style) => `\`[!${calloutType.toUpperCase()}]\` callout became ${style}-styled text: the schema has no \`callout\` block object`,
|
|
1023
|
+
"fields-dropped": (names, construct) => `Dropped ${names} from \`${construct}\`: not in the schema's \`${construct}\` fields`,
|
|
1024
|
+
"object-carrier-invalid": (kind, payload) => kind === "fence" ? `\`json:object\` fence fell back to a code block: ${describeObjectCarrierFailure(payload)}` : `\`json:object\`-tagged code span fell back to a plain code span: ${describeObjectCarrierFailure(payload)}`
|
|
1025
|
+
};
|
|
1026
|
+
/**
|
|
1027
|
+
* Names why a `json:object` payload failed to parse as an object carrier:
|
|
1028
|
+
* a payload that isn't a JSON object at all reads differently from one
|
|
1029
|
+
* that is but has no usable `_type`.
|
|
1030
|
+
*/
|
|
1031
|
+
function describeObjectCarrierFailure(payload) {
|
|
1032
|
+
let parsed;
|
|
1033
|
+
try {
|
|
1034
|
+
parsed = JSON.parse(payload);
|
|
1035
|
+
} catch {
|
|
1036
|
+
return "the payload is not valid JSON";
|
|
1037
|
+
}
|
|
1038
|
+
return typeof parsed != "object" || !parsed || Array.isArray(parsed) ? "the payload is not a JSON object" : "the payload has no string `_type`";
|
|
1039
|
+
}
|
|
1040
|
+
const droppedFieldsTag = Symbol("droppedFields");
|
|
1041
|
+
function readDroppedFields(object) {
|
|
1042
|
+
if (object) return object[droppedFieldsTag];
|
|
1043
|
+
}
|
|
1044
|
+
function buildFilteredObject(schemaDefinition, value, keyGenerator) {
|
|
1045
|
+
let filteredValue = schemaDefinition.fields.reduce((filteredValue, field) => {
|
|
1046
|
+
let fieldValue = value[field.name];
|
|
1047
|
+
return fieldValue !== void 0 && (filteredValue[field.name] = fieldValue), filteredValue;
|
|
1048
|
+
}, {}), object = {
|
|
1049
|
+
_key: keyGenerator(),
|
|
1050
|
+
_type: schemaDefinition.name,
|
|
1051
|
+
...filteredValue
|
|
1052
|
+
}, droppedKeys = Object.entries(value).filter(([, fieldValue]) => fieldValue !== void 0).map(([key]) => key).filter((key) => !(key in filteredValue));
|
|
1053
|
+
return droppedKeys.length > 0 && Object.defineProperty(object, droppedFieldsTag, {
|
|
1054
|
+
value: {
|
|
1055
|
+
construct: schemaDefinition.name,
|
|
1056
|
+
keys: droppedKeys
|
|
1057
|
+
},
|
|
1058
|
+
enumerable: !1
|
|
1059
|
+
}), object;
|
|
1060
|
+
}
|
|
615
1061
|
function buildStyleMatcher(definition) {
|
|
616
1062
|
return ({ context }) => {
|
|
617
1063
|
let schemaDefinition = context.schema.styles.find((item) => item.name === definition.name);
|
|
@@ -633,31 +1079,13 @@ function buildDecoratorMatcher(definition) {
|
|
|
633
1079
|
function buildAnnotationMatcher(definition) {
|
|
634
1080
|
return ({ context, value }) => {
|
|
635
1081
|
let schemaDefinition = context.schema.annotations.find((item) => item.name === definition.name);
|
|
636
|
-
if (
|
|
637
|
-
let filteredValue = schemaDefinition.fields.reduce((filteredValue, field) => {
|
|
638
|
-
let fieldValue = value[field.name];
|
|
639
|
-
return fieldValue !== void 0 && (filteredValue[field.name] = fieldValue), filteredValue;
|
|
640
|
-
}, {});
|
|
641
|
-
return {
|
|
642
|
-
_key: context.keyGenerator(),
|
|
643
|
-
_type: schemaDefinition.name,
|
|
644
|
-
...filteredValue
|
|
645
|
-
};
|
|
1082
|
+
if (schemaDefinition) return buildFilteredObject(schemaDefinition, value, context.keyGenerator);
|
|
646
1083
|
};
|
|
647
1084
|
}
|
|
648
1085
|
function buildObjectMatcher(definition) {
|
|
649
1086
|
return ({ context, value, isInline }) => {
|
|
650
1087
|
let schemaDefinition = (isInline ? context.schema.inlineObjects : context.schema.blockObjects).find((item) => item.name === definition.name);
|
|
651
|
-
if (
|
|
652
|
-
let filteredValue = schemaDefinition.fields.reduce((filteredValue, field) => {
|
|
653
|
-
let fieldValue = value[field.name];
|
|
654
|
-
return fieldValue !== void 0 && (filteredValue[field.name] = fieldValue), filteredValue;
|
|
655
|
-
}, {});
|
|
656
|
-
return {
|
|
657
|
-
_key: context.keyGenerator(),
|
|
658
|
-
_type: schemaDefinition.name,
|
|
659
|
-
...filteredValue
|
|
660
|
-
};
|
|
1088
|
+
if (schemaDefinition) return buildFilteredObject(schemaDefinition, value, context.keyGenerator);
|
|
661
1089
|
};
|
|
662
1090
|
}
|
|
663
1091
|
const codeBlockMatcher = ({ context, value, isInline }) => {
|
|
@@ -742,6 +1170,83 @@ function flattenTable(table, portableText) {
|
|
|
742
1170
|
for (let row of table.rows) for (let cell of row.cells) for (let block of cell.value) portableText.push(block);
|
|
743
1171
|
}
|
|
744
1172
|
/**
|
|
1173
|
+
* Truncates a degradation message's snippet to keep the thrown/reported
|
|
1174
|
+
* message readable. Truncates on character count, not word boundaries: the
|
|
1175
|
+
* snippet is a diagnostic pointer back to the source, not prose. Undefined
|
|
1176
|
+
* for empty input, so a construct with nothing to quote (an empty link's
|
|
1177
|
+
* text, say) omits `snippet` entirely instead of reporting `""`. Backs the
|
|
1178
|
+
* cut off by one unit when it would land on a lead surrogate, so a snippet
|
|
1179
|
+
* ending mid-emoji doesn't produce an unpaired surrogate. A literal newline
|
|
1180
|
+
* surviving into the snippet is escaped to `\n`, since the reported message
|
|
1181
|
+
* is one line per finding.
|
|
1182
|
+
*/
|
|
1183
|
+
function truncateSnippet(text, maxLength = 40) {
|
|
1184
|
+
if (text.length === 0) return;
|
|
1185
|
+
if (text.length <= maxLength) return text.replace(/\n/g, "\\n");
|
|
1186
|
+
let cut = maxLength, codeUnit = text.charCodeAt(cut - 1);
|
|
1187
|
+
return codeUnit >= 55296 && codeUnit <= 56319 && --cut, `${text.slice(0, cut).replace(/\n/g, "\\n")}...`;
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Concatenates the plain text between an inline open token (`strong_open`,
|
|
1191
|
+
* `em_open`, `s_open`, `link_open`) and its matching close, for use as a
|
|
1192
|
+
* degradation message snippet. Tracks nesting depth so a same-type token
|
|
1193
|
+
* nested inside itself doesn't stop the scan at the wrong close.
|
|
1194
|
+
*/
|
|
1195
|
+
function collectInlineText(children, openIndex, openType, closeType) {
|
|
1196
|
+
let depth = 1, text = "";
|
|
1197
|
+
for (let i = openIndex + 1; i < children.length; i++) {
|
|
1198
|
+
let child = children[i];
|
|
1199
|
+
if (child) {
|
|
1200
|
+
if (child.type === openType) depth++;
|
|
1201
|
+
else if (child.type === closeType) {
|
|
1202
|
+
if (depth--, depth === 0) break;
|
|
1203
|
+
} else child.type === "text" ? text += child.content : child.type === "softbreak" ? text += " " : child.type === "hardbreak" && (text += "\n");
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return text;
|
|
1207
|
+
}
|
|
1208
|
+
function capList(values) {
|
|
1209
|
+
if (values.length <= 5) return values.join(", ");
|
|
1210
|
+
let shown = values.slice(0, 5), more = values.length - 5;
|
|
1211
|
+
return `${shown.join(", ")}, and ${more} more`;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Builds the canonical grouped message reported alongside a non-empty
|
|
1215
|
+
* `degradations` array: identical (`type`, `message`) pairs collapse into one
|
|
1216
|
+
* line, so a document with the same missing decorator on three spans
|
|
1217
|
+
* doesn't repeat the same sentence three times. Groups sort by their
|
|
1218
|
+
* earliest-lined entry so the message reads top-to-bottom regardless of walk
|
|
1219
|
+
* order: nested constructs (a blockquote inside a blockquote, say) report
|
|
1220
|
+
* the innermost closing first even though it opened last, and that line may
|
|
1221
|
+
* arrive after another entry already in the group. Groups without any lined
|
|
1222
|
+
* entry sort last, in their relative encounter order.
|
|
1223
|
+
*/
|
|
1224
|
+
function buildDegradationMessage(degradations) {
|
|
1225
|
+
let groups = [], groupIndexByKey = /* @__PURE__ */ new Map();
|
|
1226
|
+
for (let degradation of degradations) {
|
|
1227
|
+
let key = `${degradation.type}\u0000${degradation.message}`, groupIndex = groupIndexByKey.get(key);
|
|
1228
|
+
groupIndex === void 0 && (groupIndex = groups.length, groupIndexByKey.set(key, groupIndex), groups.push({
|
|
1229
|
+
base: degradation.message,
|
|
1230
|
+
entries: []
|
|
1231
|
+
})), groups[groupIndex].entries.push(degradation);
|
|
1232
|
+
}
|
|
1233
|
+
let minLine = (entries) => {
|
|
1234
|
+
let definedLines = entries.map((entry) => entry.line).filter((line) => line !== void 0);
|
|
1235
|
+
return definedLines.length > 0 ? Math.min(...definedLines) : void 0;
|
|
1236
|
+
};
|
|
1237
|
+
return ["Markdown could not be converted without loss:", ...[...groups].sort((a, b) => {
|
|
1238
|
+
let lineA = minLine(a.entries), lineB = minLine(b.entries);
|
|
1239
|
+
return lineA === void 0 ? lineB === void 0 ? 0 : 1 : lineB === void 0 ? -1 : lineA - lineB;
|
|
1240
|
+
}).map((group) => {
|
|
1241
|
+
if (group.entries.length === 1) {
|
|
1242
|
+
let event = group.entries[0], snippetPart = event.snippet === void 0 ? "" : ` ("${event.snippet}")`;
|
|
1243
|
+
return event.line === void 0 ? `- ${event.message}${snippetPart}` : `- line ${event.line}: ${event.message}${snippetPart}`;
|
|
1244
|
+
}
|
|
1245
|
+
let snippets = group.entries.map((entry) => entry.snippet).filter((snippet) => snippet !== void 0), linesAscending = [...new Set(group.entries.map((entry) => entry.line).filter((line) => line !== void 0).sort((a, b) => a - b))], count = group.entries.length, suffix = snippets.length === count ? `(${count}\u00d7: ${capList(snippets.map((snippet) => `"${snippet}"`))})` : linesAscending.length > 0 ? `(${count}\u00d7: lines ${capList(linesAscending.map(String))})` : `(${count}\u00d7)`;
|
|
1246
|
+
return `- ${group.base} ${suffix}`;
|
|
1247
|
+
})].join("\n");
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
745
1250
|
* Converts a markdown string to an array of Portable Text blocks.
|
|
746
1251
|
*
|
|
747
1252
|
* @public
|
|
@@ -767,11 +1272,46 @@ function markdownToPortableText(markdown, options) {
|
|
|
767
1272
|
...defaultOptions.types,
|
|
768
1273
|
...options?.types
|
|
769
1274
|
}
|
|
1275
|
+
}, degradationEvents = [], report = (event) => {
|
|
1276
|
+
degradationEvents.push(event);
|
|
1277
|
+
}, lineOf = (candidateToken) => candidateToken?.map ? candidateToken.map[0] + 1 : void 0, reportStyleFallback = (name, line, snippet) => {
|
|
1278
|
+
if (/^h[1-6]$/.test(name)) {
|
|
1279
|
+
let truncated = snippet === void 0 ? void 0 : truncateSnippet(snippet);
|
|
1280
|
+
report({
|
|
1281
|
+
type: "style-fallback",
|
|
1282
|
+
message: degradationMessage["style-fallback"](name),
|
|
1283
|
+
line,
|
|
1284
|
+
snippet: truncated
|
|
1285
|
+
});
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (name === "blockquote") {
|
|
1289
|
+
report({
|
|
1290
|
+
type: "style-fallback",
|
|
1291
|
+
message: degradationMessage["style-fallback"](name),
|
|
1292
|
+
line
|
|
1293
|
+
});
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
report({
|
|
1297
|
+
type: "style-fallback",
|
|
1298
|
+
message: degradationMessage["style-fallback"](name),
|
|
1299
|
+
line
|
|
1300
|
+
});
|
|
1301
|
+
}, reportFieldsDropped = (object, line) => {
|
|
1302
|
+
let dropped = readDroppedFields(object);
|
|
1303
|
+
if (!dropped) return;
|
|
1304
|
+
let names = dropped.keys.map((key) => `\`${key}\``).join(", ");
|
|
1305
|
+
report({
|
|
1306
|
+
type: "fields-dropped",
|
|
1307
|
+
message: degradationMessage["fields-dropped"](names, dropped.construct),
|
|
1308
|
+
line
|
|
1309
|
+
});
|
|
770
1310
|
}, tokens = markdownit({
|
|
771
1311
|
html: !0,
|
|
772
1312
|
linkify: !0,
|
|
773
1313
|
typographer: !1
|
|
774
|
-
}).enable(["strikethrough", "table"]).use(alert).parse(markdown, {}), taskCheckedByListItemIndex = /* @__PURE__ */ new Map();
|
|
1314
|
+
}).enable(["strikethrough", "table"]).use(alert).parse(markdown, {}), taskCheckedByListItemIndex = /* @__PURE__ */ new Map(), taskItemTextByListItemIndex = /* @__PURE__ */ new Map();
|
|
775
1315
|
for (let i = 0; i < tokens.length; i++) {
|
|
776
1316
|
if (tokens[i]?.type !== "list_item_open") continue;
|
|
777
1317
|
let inlineIndex = -1;
|
|
@@ -791,11 +1331,11 @@ function markdownToPortableText(markdown, options) {
|
|
|
791
1331
|
let match = inlineToken.content.match(/^\[([ xX])\] /);
|
|
792
1332
|
if (!match) continue;
|
|
793
1333
|
let checked = match[1] !== " ";
|
|
794
|
-
taskCheckedByListItemIndex.set(i, checked), inlineToken.content = inlineToken.content.slice(match[0].length);
|
|
1334
|
+
taskCheckedByListItemIndex.set(i, checked), inlineToken.content = inlineToken.content.slice(match[0].length), taskItemTextByListItemIndex.set(i, inlineToken.content);
|
|
795
1335
|
let firstChild = inlineToken.children?.[0];
|
|
796
1336
|
firstChild && typeof firstChild.content == "string" && (firstChild.content = firstChild.content.slice(match[0].length));
|
|
797
1337
|
}
|
|
798
|
-
let portableText = [], currentBlock = null, currentListStack = [], markDefRefs = [], currentMarkDefs = [], currentBlockquoteStyle = null, inListItem = !1, calloutStartIndex = null, calloutStartTarget = null, calloutType = null, blockquoteStack = [], currentTable = null, currentTableRow = null, inTableHead = !1, listContainerStack = [], blockTarget = () => {
|
|
1338
|
+
let portableText = [], currentBlock = null, currentListStack = [], markDefRefs = [], currentMarkDefs = [], currentBlockquoteStyle = null, inListItem = !1, currentBlockTookBlockquoteStyle = !1, currentBlockIsPlainParagraph = !1, plainParagraphBlocks = /* @__PURE__ */ new WeakSet(), calloutStartIndex = null, calloutStartTarget = null, calloutType = null, calloutStartLine, calloutPendingStyleFallbacks = [], blockquoteStack = [], currentTable = null, currentTableRow = null, inTableHead = !1, demotedTableImages = /* @__PURE__ */ new WeakMap(), listContainerStack = [], pendingListFlattenedStack = [], taskInfoByListItem = /* @__PURE__ */ new WeakMap(), blockTarget = () => {
|
|
799
1339
|
for (let i = listContainerStack.length - 1; i >= 0; i--) {
|
|
800
1340
|
let frame = listContainerStack[i];
|
|
801
1341
|
if (frame && frame.currentItem) return frame.currentItem.content;
|
|
@@ -803,26 +1343,35 @@ function markdownToPortableText(markdown, options) {
|
|
|
803
1343
|
return portableText;
|
|
804
1344
|
}, pushBlock = (block) => {
|
|
805
1345
|
blockTarget().push(block);
|
|
806
|
-
}, startBlock = (style) => {
|
|
1346
|
+
}, startBlock = (style, provenance) => {
|
|
807
1347
|
flushBlock(), currentBlock = {
|
|
808
1348
|
_type: "block",
|
|
809
1349
|
style,
|
|
810
1350
|
children: [],
|
|
811
1351
|
_key: consolidatedOptions.keyGenerator(),
|
|
812
1352
|
markDefs: []
|
|
813
|
-
}, currentMarkDefs = [];
|
|
1353
|
+
}, currentMarkDefs = [], currentBlockTookBlockquoteStyle = provenance?.tookBlockquoteStyle ?? !1, currentBlockIsPlainParagraph = provenance?.isPlainParagraph ?? !1;
|
|
814
1354
|
}, flushBlock = () => {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1355
|
+
if (currentBlock) {
|
|
1356
|
+
if (calloutPendingStyleFallbacks.length > 0 && currentBlockTookBlockquoteStyle) {
|
|
1357
|
+
for (let name of calloutPendingStyleFallbacks) reportStyleFallback(name, calloutStartLine);
|
|
1358
|
+
calloutPendingStyleFallbacks = [];
|
|
1359
|
+
}
|
|
1360
|
+
currentBlock.children.length === 0 && currentBlock.children.push({
|
|
1361
|
+
_type: consolidatedOptions.schema.span.name,
|
|
1362
|
+
_key: consolidatedOptions.keyGenerator(),
|
|
1363
|
+
text: "",
|
|
1364
|
+
marks: []
|
|
1365
|
+
}), currentBlock.markDefs = currentMarkDefs, currentBlockIsPlainParagraph && plainParagraphBlocks.add(currentBlock), pushBlock(currentBlock), currentBlock = null, currentMarkDefs = [];
|
|
1366
|
+
}
|
|
821
1367
|
}, addSpan = (text) => {
|
|
822
1368
|
if (text.length === 0) return;
|
|
823
1369
|
if (!currentBlock) {
|
|
824
1370
|
let style = currentBlockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
825
|
-
style ? startBlock(style
|
|
1371
|
+
style ? startBlock(style, {
|
|
1372
|
+
tookBlockquoteStyle: currentBlockquoteStyle !== null,
|
|
1373
|
+
isPlainParagraph: !0
|
|
1374
|
+
}) : (reportStyleFallback("normal"), startBlock("normal", { isPlainParagraph: !0 }));
|
|
826
1375
|
}
|
|
827
1376
|
if (!currentBlock) throw Error("Expected current block");
|
|
828
1377
|
let lastChild = currentBlock.children.at(-1);
|
|
@@ -835,7 +1384,7 @@ function markdownToPortableText(markdown, options) {
|
|
|
835
1384
|
}, listLevel = () => currentListStack.length, ensureListBlock = (listItem, checked) => {
|
|
836
1385
|
if (!currentBlock) {
|
|
837
1386
|
let style = currentBlockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
838
|
-
style ? startBlock(style
|
|
1387
|
+
style ? startBlock(style, { tookBlockquoteStyle: currentBlockquoteStyle !== null }) : (reportStyleFallback("normal"), startBlock("normal"));
|
|
839
1388
|
}
|
|
840
1389
|
if (!currentBlock) throw Error("Expected current block");
|
|
841
1390
|
(currentBlock.listItem !== listItem || currentBlock.level !== listLevel()) && (currentBlock.listItem = listItem, currentBlock.level = listLevel()), checked !== void 0 && (currentBlock.checked = checked);
|
|
@@ -846,7 +1395,13 @@ function markdownToPortableText(markdown, options) {
|
|
|
846
1395
|
case "paragraph_open": {
|
|
847
1396
|
if (inListItem) {
|
|
848
1397
|
if (listContainerStack.at(-1)) {
|
|
849
|
-
|
|
1398
|
+
if (!currentBlock) {
|
|
1399
|
+
let style = currentBlockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1400
|
+
style || reportStyleFallback("normal", lineOf(token)), startBlock(style ?? "normal", {
|
|
1401
|
+
tookBlockquoteStyle: currentBlockquoteStyle !== null,
|
|
1402
|
+
isPlainParagraph: !0
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
850
1405
|
break;
|
|
851
1406
|
}
|
|
852
1407
|
if (!currentBlock) {
|
|
@@ -857,10 +1412,13 @@ function markdownToPortableText(markdown, options) {
|
|
|
857
1412
|
}
|
|
858
1413
|
let style = currentBlockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
859
1414
|
if (!style) {
|
|
860
|
-
|
|
1415
|
+
reportStyleFallback("normal", lineOf(token)), startBlock("normal", { isPlainParagraph: !0 });
|
|
861
1416
|
break;
|
|
862
1417
|
}
|
|
863
|
-
startBlock(style
|
|
1418
|
+
startBlock(style, {
|
|
1419
|
+
tookBlockquoteStyle: currentBlockquoteStyle !== null,
|
|
1420
|
+
isPlainParagraph: !0
|
|
1421
|
+
});
|
|
864
1422
|
break;
|
|
865
1423
|
}
|
|
866
1424
|
case "paragraph_close":
|
|
@@ -878,9 +1436,11 @@ function markdownToPortableText(markdown, options) {
|
|
|
878
1436
|
4: consolidatedOptions.block.h4,
|
|
879
1437
|
5: consolidatedOptions.block.h5,
|
|
880
1438
|
6: consolidatedOptions.block.h6
|
|
881
|
-
}[level],
|
|
1439
|
+
}[level], headingStyle = headingMatcher?.({ context: { schema: consolidatedOptions.schema } });
|
|
1440
|
+
headingStyle || reportStyleFallback(`h${level}`, lineOf(token), tokens[tokenIndex + 1]?.content);
|
|
1441
|
+
let style = headingStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
882
1442
|
if (!style) {
|
|
883
|
-
|
|
1443
|
+
reportStyleFallback("normal", lineOf(token)), startBlock("normal");
|
|
884
1444
|
break;
|
|
885
1445
|
}
|
|
886
1446
|
startBlock(style);
|
|
@@ -889,17 +1449,22 @@ function markdownToPortableText(markdown, options) {
|
|
|
889
1449
|
case "heading_close":
|
|
890
1450
|
flushBlock();
|
|
891
1451
|
break;
|
|
892
|
-
case "blockquote_open":
|
|
1452
|
+
case "blockquote_open": {
|
|
893
1453
|
if (flushBlock(), consolidatedOptions.types.blockquote) {
|
|
894
1454
|
let startTarget = blockTarget();
|
|
895
1455
|
blockquoteStack.push({
|
|
896
1456
|
startTarget,
|
|
897
|
-
startIndex: startTarget.length
|
|
1457
|
+
startIndex: startTarget.length,
|
|
1458
|
+
line: lineOf(token)
|
|
898
1459
|
});
|
|
899
1460
|
break;
|
|
900
1461
|
}
|
|
901
|
-
|
|
1462
|
+
let blockquoteStyle = consolidatedOptions.block.blockquote({ context: { schema: consolidatedOptions.schema } });
|
|
1463
|
+
blockquoteStyle || reportStyleFallback("blockquote", lineOf(token));
|
|
1464
|
+
let style = blockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1465
|
+
style || reportStyleFallback("normal", lineOf(token)), currentBlockquoteStyle = style ?? "normal";
|
|
902
1466
|
break;
|
|
1467
|
+
}
|
|
903
1468
|
case "blockquote_close":
|
|
904
1469
|
if (flushBlock(), consolidatedOptions.types.blockquote && blockquoteStack.length > 0) {
|
|
905
1470
|
let frame = blockquoteStack.pop();
|
|
@@ -914,11 +1479,18 @@ function markdownToPortableText(markdown, options) {
|
|
|
914
1479
|
});
|
|
915
1480
|
if (blockquoteObject) pushBlock(blockquoteObject);
|
|
916
1481
|
else {
|
|
917
|
-
let blockquoteStyle = consolidatedOptions.block.blockquote({ context: { schema: consolidatedOptions.schema } })
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1482
|
+
let blockquoteStyle = consolidatedOptions.block.blockquote({ context: { schema: consolidatedOptions.schema } });
|
|
1483
|
+
blockquoteStyle || reportStyleFallback("blockquote", frame.line);
|
|
1484
|
+
let resolvedStyle = blockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1485
|
+
resolvedStyle || reportStyleFallback("normal", frame.line);
|
|
1486
|
+
let fallbackStyle = resolvedStyle ?? "blockquote";
|
|
1487
|
+
for (let block of contentBlocks) if (block._type === "block" && plainParagraphBlocks.has(block)) {
|
|
1488
|
+
let restyledBlock = {
|
|
1489
|
+
...block,
|
|
1490
|
+
style: fallbackStyle
|
|
1491
|
+
};
|
|
1492
|
+
plainParagraphBlocks.add(restyledBlock), pushBlock(restyledBlock);
|
|
1493
|
+
} else pushBlock(block);
|
|
922
1494
|
}
|
|
923
1495
|
}
|
|
924
1496
|
break;
|
|
@@ -930,16 +1502,21 @@ function markdownToPortableText(markdown, options) {
|
|
|
930
1502
|
listContainerStack.push({
|
|
931
1503
|
kind: "bullet",
|
|
932
1504
|
items: [],
|
|
933
|
-
currentItem: null
|
|
934
|
-
|
|
1505
|
+
currentItem: null,
|
|
1506
|
+
line: lineOf(token)
|
|
1507
|
+
}), currentListStack.push(null), pendingListFlattenedStack.push(null);
|
|
935
1508
|
break;
|
|
936
1509
|
}
|
|
937
1510
|
let listItem = consolidatedOptions.listItem.bullet({ context: { schema: consolidatedOptions.schema } });
|
|
938
1511
|
if (listContainerStack.push(null), !listItem) {
|
|
939
|
-
|
|
1512
|
+
pendingListFlattenedStack.push({
|
|
1513
|
+
line: lineOf(token),
|
|
1514
|
+
kindName: "bullet",
|
|
1515
|
+
reported: !1
|
|
1516
|
+
}), currentListStack.push(null);
|
|
940
1517
|
break;
|
|
941
1518
|
}
|
|
942
|
-
currentListStack.push(listItem);
|
|
1519
|
+
pendingListFlattenedStack.push(null), currentListStack.push(listItem);
|
|
943
1520
|
break;
|
|
944
1521
|
}
|
|
945
1522
|
case "ordered_list_open": {
|
|
@@ -947,22 +1524,27 @@ function markdownToPortableText(markdown, options) {
|
|
|
947
1524
|
listContainerStack.push({
|
|
948
1525
|
kind: "number",
|
|
949
1526
|
items: [],
|
|
950
|
-
currentItem: null
|
|
951
|
-
|
|
1527
|
+
currentItem: null,
|
|
1528
|
+
line: lineOf(token)
|
|
1529
|
+
}), currentListStack.push(null), pendingListFlattenedStack.push(null);
|
|
952
1530
|
break;
|
|
953
1531
|
}
|
|
954
1532
|
let listItem = consolidatedOptions.listItem.number({ context: { schema: consolidatedOptions.schema } });
|
|
955
1533
|
if (listContainerStack.push(null), !listItem) {
|
|
956
|
-
|
|
1534
|
+
pendingListFlattenedStack.push({
|
|
1535
|
+
line: lineOf(token),
|
|
1536
|
+
kindName: "number",
|
|
1537
|
+
reported: !1
|
|
1538
|
+
}), currentListStack.push(null);
|
|
957
1539
|
break;
|
|
958
1540
|
}
|
|
959
|
-
currentListStack.push(listItem);
|
|
1541
|
+
pendingListFlattenedStack.push(null), currentListStack.push(listItem);
|
|
960
1542
|
break;
|
|
961
1543
|
}
|
|
962
1544
|
case "bullet_list_close":
|
|
963
1545
|
case "ordered_list_close": {
|
|
964
1546
|
let frame = listContainerStack.pop();
|
|
965
|
-
if (currentListStack.pop(), frame && consolidatedOptions.types.list) {
|
|
1547
|
+
if (currentListStack.pop(), pendingListFlattenedStack.pop(), frame && consolidatedOptions.types.list) {
|
|
966
1548
|
let kind = frame.items.some((item) => "checked" in item) ? "task" : frame.kind, listObject = consolidatedOptions.types.list({
|
|
967
1549
|
context: {
|
|
968
1550
|
schema: consolidatedOptions.schema,
|
|
@@ -976,13 +1558,45 @@ function markdownToPortableText(markdown, options) {
|
|
|
976
1558
|
});
|
|
977
1559
|
if (listObject) pushBlock(listObject);
|
|
978
1560
|
else {
|
|
979
|
-
let
|
|
980
|
-
for (let item of frame.items)
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1561
|
+
let kindListItem = (frame.kind === "number" ? consolidatedOptions.listItem.number : consolidatedOptions.listItem.bullet)({ context: { schema: consolidatedOptions.schema } }) ?? null, taskListItemType = consolidatedOptions.listItem.task?.({ context: { schema: consolidatedOptions.schema } }) ?? null, kindName = frame.kind === "number" ? "number" : "bullet", level = listContainerStack.length + 1, flattenedReported = !1;
|
|
1562
|
+
for (let item of frame.items) {
|
|
1563
|
+
let itemListType = kindListItem, itemChecked;
|
|
1564
|
+
if (item.checked !== void 0) {
|
|
1565
|
+
if (taskListItemType) itemListType = taskListItemType, itemChecked = item.checked;
|
|
1566
|
+
else if (kindListItem !== null) {
|
|
1567
|
+
let info = taskInfoByListItem.get(item);
|
|
1568
|
+
report({
|
|
1569
|
+
type: "task-checkbox-stripped",
|
|
1570
|
+
message: degradationMessage["task-checkbox-stripped"](item.checked),
|
|
1571
|
+
line: info?.line,
|
|
1572
|
+
snippet: info?.snippet
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
itemListType === null && !flattenedReported && (report({
|
|
1577
|
+
type: "list-flattened",
|
|
1578
|
+
message: degradationMessage["list-flattened"](kindName),
|
|
1579
|
+
line: frame.line
|
|
1580
|
+
}), flattenedReported = !0);
|
|
1581
|
+
let mergeTarget = null;
|
|
1582
|
+
for (let block of item.content) {
|
|
1583
|
+
if (!(itemListType !== null && block._type === "block" && !("listItem" in block) && !/^h[1-6]$/.test(block.style ?? "") && plainParagraphBlocks.has(block))) {
|
|
1584
|
+
mergeTarget = null, pushBlock(block);
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
let textBlock = block;
|
|
1588
|
+
if (mergeTarget && mergeTarget.style === textBlock.style) {
|
|
1589
|
+
mergeTarget.children.push(...textBlock.children), mergeTarget.markDefs = [...mergeTarget.markDefs ?? [], ...textBlock.markDefs ?? []];
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
mergeTarget = {
|
|
1593
|
+
...textBlock,
|
|
1594
|
+
listItem: itemListType,
|
|
1595
|
+
level,
|
|
1596
|
+
...itemChecked === void 0 ? {} : { checked: itemChecked }
|
|
1597
|
+
}, pushBlock(mergeTarget);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
986
1600
|
}
|
|
987
1601
|
}
|
|
988
1602
|
break;
|
|
@@ -996,7 +1610,10 @@ function markdownToPortableText(markdown, options) {
|
|
|
996
1610
|
_key: consolidatedOptions.keyGenerator(),
|
|
997
1611
|
...taskChecked === void 0 ? {} : { checked: taskChecked },
|
|
998
1612
|
content: []
|
|
999
|
-
},
|
|
1613
|
+
}, taskChecked !== void 0 && taskInfoByListItem.set(frame.currentItem, {
|
|
1614
|
+
line: lineOf(token),
|
|
1615
|
+
snippet: truncateSnippet(taskItemTextByListItemIndex.get(tokenIndex) ?? "")
|
|
1616
|
+
}), inListItem = !0;
|
|
1000
1617
|
break;
|
|
1001
1618
|
}
|
|
1002
1619
|
let baseListType = currentListStack.at(-1);
|
|
@@ -1007,10 +1624,25 @@ function markdownToPortableText(markdown, options) {
|
|
|
1007
1624
|
taskListType && (listType = taskListType, checked = taskChecked);
|
|
1008
1625
|
}
|
|
1009
1626
|
if (listType === null) {
|
|
1627
|
+
let pendingFlattened = pendingListFlattenedStack.at(-1);
|
|
1628
|
+
pendingFlattened && !pendingFlattened.reported && (report({
|
|
1629
|
+
type: "list-flattened",
|
|
1630
|
+
message: degradationMessage["list-flattened"](pendingFlattened.kindName),
|
|
1631
|
+
line: pendingFlattened.line
|
|
1632
|
+
}), pendingFlattened.reported = !0);
|
|
1010
1633
|
let style = currentBlockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1011
|
-
style ? startBlock(style
|
|
1634
|
+
style ? startBlock(style, { tookBlockquoteStyle: currentBlockquoteStyle !== null }) : (reportStyleFallback("normal", lineOf(token)), startBlock("normal")), inListItem = !0;
|
|
1012
1635
|
break;
|
|
1013
1636
|
}
|
|
1637
|
+
if (taskChecked !== void 0 && checked === void 0) {
|
|
1638
|
+
let snippet = truncateSnippet(taskItemTextByListItemIndex.get(tokenIndex) ?? "");
|
|
1639
|
+
report({
|
|
1640
|
+
type: "task-checkbox-stripped",
|
|
1641
|
+
message: degradationMessage["task-checkbox-stripped"](taskChecked),
|
|
1642
|
+
line: lineOf(token),
|
|
1643
|
+
snippet
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1014
1646
|
ensureListBlock(listType, checked), inListItem = !0;
|
|
1015
1647
|
break;
|
|
1016
1648
|
}
|
|
@@ -1025,7 +1657,21 @@ function markdownToPortableText(markdown, options) {
|
|
|
1025
1657
|
}
|
|
1026
1658
|
case "fence": {
|
|
1027
1659
|
flushBlock();
|
|
1028
|
-
let language = token.info.trim() || void 0, code = token.content.replace(/\n$/, "")
|
|
1660
|
+
let language = token.info.trim() || void 0, code = token.content.replace(/\n$/, "");
|
|
1661
|
+
if (language === "json:object") {
|
|
1662
|
+
let objectValue = parseJsonObjectFence(code);
|
|
1663
|
+
if (objectValue) {
|
|
1664
|
+
pushBlock(objectValue);
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
report({
|
|
1668
|
+
type: "object-carrier-invalid",
|
|
1669
|
+
message: degradationMessage["object-carrier-invalid"]("fence", code),
|
|
1670
|
+
line: lineOf(token),
|
|
1671
|
+
snippet: truncateSnippet(code)
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
let codeObject = consolidatedOptions.types.code({
|
|
1029
1675
|
context: {
|
|
1030
1676
|
schema: consolidatedOptions.schema,
|
|
1031
1677
|
keyGenerator: consolidatedOptions.keyGenerator
|
|
@@ -1037,11 +1683,18 @@ function markdownToPortableText(markdown, options) {
|
|
|
1037
1683
|
isInline: !1
|
|
1038
1684
|
});
|
|
1039
1685
|
if (!codeObject) {
|
|
1686
|
+
let snippet = truncateSnippet(code.split("\n")[0] ?? "");
|
|
1687
|
+
report({
|
|
1688
|
+
type: "code-block-to-text",
|
|
1689
|
+
message: degradationMessage["code-block-to-text"](language),
|
|
1690
|
+
line: lineOf(token),
|
|
1691
|
+
snippet
|
|
1692
|
+
});
|
|
1040
1693
|
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1041
|
-
style ? startBlock(style) : (
|
|
1694
|
+
style ? startBlock(style) : (reportStyleFallback("normal", lineOf(token)), startBlock("normal")), addSpan(code), flushBlock();
|
|
1042
1695
|
break;
|
|
1043
1696
|
}
|
|
1044
|
-
pushBlock(codeObject);
|
|
1697
|
+
reportFieldsDropped(codeObject, lineOf(token)), pushBlock(codeObject);
|
|
1045
1698
|
break;
|
|
1046
1699
|
}
|
|
1047
1700
|
case "hr": {
|
|
@@ -1055,8 +1708,13 @@ function markdownToPortableText(markdown, options) {
|
|
|
1055
1708
|
isInline: !1
|
|
1056
1709
|
});
|
|
1057
1710
|
if (!hrObject) {
|
|
1711
|
+
report({
|
|
1712
|
+
type: "horizontal-rule-to-text",
|
|
1713
|
+
message: degradationMessage["horizontal-rule-to-text"],
|
|
1714
|
+
line: lineOf(token)
|
|
1715
|
+
});
|
|
1058
1716
|
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1059
|
-
style ? startBlock(style) : (
|
|
1717
|
+
style ? startBlock(style) : (reportStyleFallback("normal", lineOf(token)), startBlock("normal")), addSpan("---"), flushBlock();
|
|
1060
1718
|
break;
|
|
1061
1719
|
}
|
|
1062
1720
|
pushBlock(hrObject);
|
|
@@ -1075,11 +1733,18 @@ function markdownToPortableText(markdown, options) {
|
|
|
1075
1733
|
isInline: !1
|
|
1076
1734
|
});
|
|
1077
1735
|
if (!htmlObject) {
|
|
1736
|
+
let snippet = truncateSnippet(htmlContent);
|
|
1737
|
+
report({
|
|
1738
|
+
type: "html-block-to-text",
|
|
1739
|
+
message: degradationMessage["html-block-to-text"],
|
|
1740
|
+
line: lineOf(token),
|
|
1741
|
+
snippet
|
|
1742
|
+
});
|
|
1078
1743
|
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1079
|
-
style ? startBlock(style) : (
|
|
1744
|
+
style ? startBlock(style) : (reportStyleFallback("normal", lineOf(token)), startBlock("normal")), addSpan(htmlContent), flushBlock();
|
|
1080
1745
|
break;
|
|
1081
1746
|
}
|
|
1082
|
-
pushBlock(htmlObject);
|
|
1747
|
+
reportFieldsDropped(htmlObject, lineOf(token)), pushBlock(htmlObject);
|
|
1083
1748
|
break;
|
|
1084
1749
|
}
|
|
1085
1750
|
case "code_block": {
|
|
@@ -1095,10 +1760,17 @@ function markdownToPortableText(markdown, options) {
|
|
|
1095
1760
|
},
|
|
1096
1761
|
isInline: !1
|
|
1097
1762
|
});
|
|
1098
|
-
if (codeObject) pushBlock(codeObject);
|
|
1763
|
+
if (codeObject) reportFieldsDropped(codeObject, lineOf(token)), pushBlock(codeObject);
|
|
1099
1764
|
else {
|
|
1765
|
+
let snippet = truncateSnippet(code.split("\n")[0] ?? "");
|
|
1766
|
+
report({
|
|
1767
|
+
type: "code-block-to-text",
|
|
1768
|
+
message: degradationMessage["code-block-to-text"](void 0),
|
|
1769
|
+
line: lineOf(token),
|
|
1770
|
+
snippet
|
|
1771
|
+
});
|
|
1100
1772
|
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1101
|
-
style ? startBlock(style) : (
|
|
1773
|
+
style ? startBlock(style) : (reportStyleFallback("normal", lineOf(token)), startBlock("normal")), addSpan(code), flushBlock();
|
|
1102
1774
|
}
|
|
1103
1775
|
break;
|
|
1104
1776
|
}
|
|
@@ -1107,7 +1779,8 @@ function markdownToPortableText(markdown, options) {
|
|
|
1107
1779
|
rows: [],
|
|
1108
1780
|
headerRows: 0,
|
|
1109
1781
|
emptyHeaderDropped: !1,
|
|
1110
|
-
alignment: []
|
|
1782
|
+
alignment: [],
|
|
1783
|
+
line: lineOf(token)
|
|
1111
1784
|
};
|
|
1112
1785
|
break;
|
|
1113
1786
|
case "table_close":
|
|
@@ -1125,8 +1798,16 @@ function markdownToPortableText(markdown, options) {
|
|
|
1125
1798
|
},
|
|
1126
1799
|
isInline: !1
|
|
1127
1800
|
});
|
|
1128
|
-
tableObject ? pushBlock(tableObject) :
|
|
1129
|
-
|
|
1801
|
+
tableObject ? (reportFieldsDropped(tableObject, currentTable.line), pushBlock(tableObject)) : (report({
|
|
1802
|
+
type: "table-flattened",
|
|
1803
|
+
message: degradationMessage["table-flattened"],
|
|
1804
|
+
line: currentTable.line
|
|
1805
|
+
}), flattenTable(currentTable, blockTarget()));
|
|
1806
|
+
} else report({
|
|
1807
|
+
type: "table-flattened",
|
|
1808
|
+
message: degradationMessage["table-flattened"],
|
|
1809
|
+
line: currentTable.line
|
|
1810
|
+
}), flattenTable(currentTable, blockTarget());
|
|
1130
1811
|
currentTable = null;
|
|
1131
1812
|
break;
|
|
1132
1813
|
case "thead_open":
|
|
@@ -1151,7 +1832,7 @@ function markdownToPortableText(markdown, options) {
|
|
|
1151
1832
|
case "td_open": {
|
|
1152
1833
|
currentTable && inTableHead && token.type === "th_open" && currentTable.alignment.push(extractAlignmentFromStyleAttr(token.attrGet("style")));
|
|
1153
1834
|
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1154
|
-
style ? startBlock(style) : (
|
|
1835
|
+
style ? startBlock(style) : (reportStyleFallback("normal", currentTable?.line), startBlock("normal"));
|
|
1155
1836
|
break;
|
|
1156
1837
|
}
|
|
1157
1838
|
case "th_close":
|
|
@@ -1174,10 +1855,21 @@ function markdownToPortableText(markdown, options) {
|
|
|
1174
1855
|
_key: consolidatedOptions.keyGenerator(),
|
|
1175
1856
|
markDefs: []
|
|
1176
1857
|
});
|
|
1177
|
-
let
|
|
1858
|
+
let demotedInlineImages = [];
|
|
1859
|
+
for (let block of cellBlocks) if (block._type === "block" && "children" in block && Array.isArray(block.children)) for (let child of block.children) typeof child == "object" && child && demotedTableImages.has(child) && demotedInlineImages.push(child);
|
|
1860
|
+
let firstBlock = cellBlocks[0], liftedImage;
|
|
1178
1861
|
if (cellBlocks.length === 1 && firstBlock && firstBlock._type === "block" && "children" in firstBlock && Array.isArray(firstBlock.children) && firstBlock.children.length === 1) {
|
|
1179
1862
|
let onlyChild = firstBlock.children[0];
|
|
1180
|
-
typeof onlyChild == "object" && onlyChild && "_type" in onlyChild && onlyChild._type !== consolidatedOptions.schema.span.name && onlyChild._type === "image" && (cellBlocks[0] = onlyChild);
|
|
1863
|
+
typeof onlyChild == "object" && onlyChild && "_type" in onlyChild && onlyChild._type !== consolidatedOptions.schema.span.name && onlyChild._type === "image" && (cellBlocks[0] = onlyChild, liftedImage = onlyChild);
|
|
1864
|
+
}
|
|
1865
|
+
for (let demotedImage of demotedInlineImages) if (demotedImage !== liftedImage) {
|
|
1866
|
+
let { alt, src } = demotedImage;
|
|
1867
|
+
report({
|
|
1868
|
+
type: "image-block-to-inline",
|
|
1869
|
+
message: degradationMessage["image-block-to-inline"](demotedTableImages.get(demotedImage) ?? "table-cell"),
|
|
1870
|
+
line: currentTable?.line,
|
|
1871
|
+
snippet: truncateSnippet(alt || src || "")
|
|
1872
|
+
});
|
|
1181
1873
|
}
|
|
1182
1874
|
currentTableRow !== null && currentTableRow.push({
|
|
1183
1875
|
_type: "cell",
|
|
@@ -1187,7 +1879,7 @@ function markdownToPortableText(markdown, options) {
|
|
|
1187
1879
|
break;
|
|
1188
1880
|
}
|
|
1189
1881
|
case "inline": {
|
|
1190
|
-
let inTableCell = currentTableRow !== null;
|
|
1882
|
+
let inTableCell = currentTableRow !== null, inlineLine = () => lineOf(token) ?? currentTable?.line;
|
|
1191
1883
|
if (token.children?.length === 1 && token.children[0]?.type === "image") {
|
|
1192
1884
|
let imageToken = token.children[0];
|
|
1193
1885
|
if (!imageToken) break;
|
|
@@ -1204,7 +1896,7 @@ function markdownToPortableText(markdown, options) {
|
|
|
1204
1896
|
isInline: !1
|
|
1205
1897
|
});
|
|
1206
1898
|
if (blockImageObject) {
|
|
1207
|
-
inTableCell ? currentBlock && "children" in currentBlock && currentBlock.children.push(blockImageObject) : (currentBlock && "children" in currentBlock && currentBlock.children.length > 0 ? flushBlock() : (currentBlock = null, currentMarkDefs = []), pushBlock(blockImageObject));
|
|
1899
|
+
reportFieldsDropped(blockImageObject, inlineLine()), inTableCell ? (demotedTableImages.set(blockImageObject, "table-cell"), currentBlock && "children" in currentBlock && currentBlock.children.push(blockImageObject)) : (currentBlock && "children" in currentBlock && currentBlock.children.length > 0 ? flushBlock() : (currentBlock = null, currentMarkDefs = []), pushBlock(blockImageObject));
|
|
1208
1900
|
break;
|
|
1209
1901
|
}
|
|
1210
1902
|
let inlineImageObject = consolidatedOptions.types.image({
|
|
@@ -1220,165 +1912,281 @@ function markdownToPortableText(markdown, options) {
|
|
|
1220
1912
|
isInline: !0
|
|
1221
1913
|
});
|
|
1222
1914
|
if (inlineImageObject) {
|
|
1223
|
-
if (
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1915
|
+
if (reportFieldsDropped(inlineImageObject, inlineLine()), inTableCell ? demotedTableImages.set(inlineImageObject, "no-block-image") : report({
|
|
1916
|
+
type: "image-block-to-inline",
|
|
1917
|
+
message: degradationMessage["image-block-to-inline"]("no-block-image"),
|
|
1918
|
+
line: inlineLine(),
|
|
1919
|
+
snippet: truncateSnippet(alt || src)
|
|
1920
|
+
}), !currentBlock) {
|
|
1921
|
+
if (inListItem) {
|
|
1922
|
+
if (listContainerStack.at(-1)) {
|
|
1923
|
+
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1924
|
+
style || reportStyleFallback("normal", inlineLine()), startBlock(style ?? "normal");
|
|
1925
|
+
} else {
|
|
1926
|
+
let listType = currentListStack.at(-1);
|
|
1927
|
+
listType && ensureListBlock(listType);
|
|
1928
|
+
}
|
|
1929
|
+
} else {
|
|
1930
|
+
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
1931
|
+
style && startBlock(style);
|
|
1932
|
+
}
|
|
1231
1933
|
}
|
|
1232
1934
|
currentBlock && "children" in currentBlock && currentBlock.children.push(inlineImageObject);
|
|
1233
1935
|
break;
|
|
1234
1936
|
}
|
|
1235
|
-
|
|
1937
|
+
let standaloneImageSnippet = truncateSnippet(alt || src);
|
|
1938
|
+
report({
|
|
1939
|
+
type: "image-to-text",
|
|
1940
|
+
message: degradationMessage["image-to-text"],
|
|
1941
|
+
line: inlineLine(),
|
|
1942
|
+
snippet: standaloneImageSnippet
|
|
1943
|
+
}), addSpan(``);
|
|
1236
1944
|
break;
|
|
1237
1945
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1946
|
+
let inlineChildren = token.children ?? [];
|
|
1947
|
+
for (let childIndex = 0; childIndex < inlineChildren.length; childIndex++) {
|
|
1948
|
+
let childToken = inlineChildren[childIndex];
|
|
1949
|
+
if (childToken) switch (childToken.type) {
|
|
1950
|
+
case "text": {
|
|
1951
|
+
let nextToken = inlineChildren[childIndex + 1];
|
|
1952
|
+
if (childToken.content.endsWith("json:object") && nextToken?.type === "code_inline" && currentBlock && "children" in currentBlock) {
|
|
1953
|
+
let objectValue = parseJsonObjectFence(nextToken.content);
|
|
1954
|
+
if (objectValue) {
|
|
1955
|
+
let prefix = childToken.content.slice(0, -11);
|
|
1956
|
+
prefix.length > 0 && addSpan(prefix), currentBlock.children.push(objectValue), childIndex++;
|
|
1957
|
+
break;
|
|
1958
|
+
}
|
|
1959
|
+
report({
|
|
1960
|
+
type: "object-carrier-invalid",
|
|
1961
|
+
message: degradationMessage["object-carrier-invalid"]("code-span", nextToken.content),
|
|
1962
|
+
line: inlineLine(),
|
|
1963
|
+
snippet: truncateSnippet(nextToken.content)
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1249
1966
|
addSpan(childToken.content);
|
|
1250
1967
|
break;
|
|
1251
1968
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
}
|
|
1270
|
-
case "em_open": {
|
|
1271
|
-
let decorator = consolidatedOptions.marks.em({ context: { schema: consolidatedOptions.schema } });
|
|
1272
|
-
if (!decorator) break;
|
|
1273
|
-
markDefRefs.push(decorator);
|
|
1274
|
-
break;
|
|
1275
|
-
}
|
|
1276
|
-
case "em_close": {
|
|
1277
|
-
let decorator = consolidatedOptions.marks.em({ context: { schema: consolidatedOptions.schema } });
|
|
1278
|
-
if (!decorator) break;
|
|
1279
|
-
let index = markDefRefs.lastIndexOf(decorator);
|
|
1280
|
-
index !== -1 && markDefRefs.splice(index, 1);
|
|
1281
|
-
break;
|
|
1282
|
-
}
|
|
1283
|
-
case "s_open": {
|
|
1284
|
-
let decorator = consolidatedOptions.marks.strikeThrough({ context: { schema: consolidatedOptions.schema } });
|
|
1285
|
-
if (!decorator) break;
|
|
1286
|
-
markDefRefs.push(decorator);
|
|
1287
|
-
break;
|
|
1288
|
-
}
|
|
1289
|
-
case "s_close": {
|
|
1290
|
-
let decorator = consolidatedOptions.marks.strikeThrough({ context: { schema: consolidatedOptions.schema } });
|
|
1291
|
-
if (!decorator) break;
|
|
1292
|
-
let index = markDefRefs.lastIndexOf(decorator);
|
|
1293
|
-
index !== -1 && markDefRefs.splice(index, 1);
|
|
1294
|
-
break;
|
|
1295
|
-
}
|
|
1296
|
-
case "link_open": {
|
|
1297
|
-
let href = childToken.attrs?.find(([name]) => name === "href")?.at(1);
|
|
1298
|
-
if (!href) break;
|
|
1299
|
-
let title = childToken.attrs?.find(([name]) => name === "title")?.at(1), linkObject = consolidatedOptions.marks.link({
|
|
1300
|
-
context: {
|
|
1301
|
-
schema: consolidatedOptions.schema,
|
|
1302
|
-
keyGenerator: consolidatedOptions.keyGenerator
|
|
1303
|
-
},
|
|
1304
|
-
value: {
|
|
1305
|
-
href,
|
|
1306
|
-
title
|
|
1969
|
+
case "softbreak":
|
|
1970
|
+
addSpan(" ");
|
|
1971
|
+
break;
|
|
1972
|
+
case "hardbreak":
|
|
1973
|
+
addSpan("\n");
|
|
1974
|
+
break;
|
|
1975
|
+
case "code_inline": {
|
|
1976
|
+
let decorator = consolidatedOptions.marks.code({ context: { schema: consolidatedOptions.schema } });
|
|
1977
|
+
if (!decorator) {
|
|
1978
|
+
let codeSnippet = truncateSnippet(childToken.content);
|
|
1979
|
+
report({
|
|
1980
|
+
type: "decorator-dropped",
|
|
1981
|
+
message: degradationMessage["decorator-dropped"]("code"),
|
|
1982
|
+
line: inlineLine(),
|
|
1983
|
+
snippet: codeSnippet
|
|
1984
|
+
}), addSpan(childToken.content);
|
|
1985
|
+
break;
|
|
1307
1986
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
break;
|
|
1312
|
-
}
|
|
1313
|
-
case "link_close": {
|
|
1314
|
-
let markDefKeys = new Set(currentMarkDefs.map((d) => d._key)), lastLinkIndex;
|
|
1315
|
-
for (let markDefRef of markDefRefs.reverse()) if (markDefKeys.has(markDefRef)) {
|
|
1316
|
-
lastLinkIndex = markDefRefs.indexOf(markDefRef);
|
|
1987
|
+
markDefRefs.push(decorator), addSpan(childToken.content);
|
|
1988
|
+
let index = markDefRefs.lastIndexOf(decorator);
|
|
1989
|
+
index !== -1 && markDefRefs.splice(index, 1);
|
|
1317
1990
|
break;
|
|
1318
1991
|
}
|
|
1319
|
-
|
|
1320
|
-
let
|
|
1321
|
-
|
|
1992
|
+
case "strong_open": {
|
|
1993
|
+
let decorator = consolidatedOptions.marks.strong({ context: { schema: consolidatedOptions.schema } });
|
|
1994
|
+
if (!decorator) {
|
|
1995
|
+
let strongSnippet = truncateSnippet(collectInlineText(inlineChildren, childIndex, "strong_open", "strong_close"));
|
|
1996
|
+
report({
|
|
1997
|
+
type: "decorator-dropped",
|
|
1998
|
+
message: degradationMessage["decorator-dropped"]("strong"),
|
|
1999
|
+
line: inlineLine(),
|
|
2000
|
+
snippet: strongSnippet
|
|
2001
|
+
});
|
|
2002
|
+
break;
|
|
2003
|
+
}
|
|
2004
|
+
markDefRefs.push(decorator);
|
|
2005
|
+
break;
|
|
1322
2006
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
style ? startBlock(style) : (console.warn("No default style found, using \"normal\""), startBlock("normal"));
|
|
2007
|
+
case "strong_close": {
|
|
2008
|
+
let decorator = consolidatedOptions.marks.strong({ context: { schema: consolidatedOptions.schema } });
|
|
2009
|
+
if (!decorator) break;
|
|
2010
|
+
let index = markDefRefs.lastIndexOf(decorator);
|
|
2011
|
+
index !== -1 && markDefRefs.splice(index, 1);
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
case "em_open": {
|
|
2015
|
+
let decorator = consolidatedOptions.marks.em({ context: { schema: consolidatedOptions.schema } });
|
|
2016
|
+
if (!decorator) {
|
|
2017
|
+
let emSnippet = truncateSnippet(collectInlineText(inlineChildren, childIndex, "em_open", "em_close"));
|
|
2018
|
+
report({
|
|
2019
|
+
type: "decorator-dropped",
|
|
2020
|
+
message: degradationMessage["decorator-dropped"]("em"),
|
|
2021
|
+
line: inlineLine(),
|
|
2022
|
+
snippet: emSnippet
|
|
2023
|
+
});
|
|
2024
|
+
break;
|
|
1342
2025
|
}
|
|
1343
|
-
|
|
1344
|
-
currentBlock.children.push(inlineImageObject);
|
|
2026
|
+
markDefRefs.push(decorator);
|
|
1345
2027
|
break;
|
|
1346
2028
|
}
|
|
1347
|
-
|
|
1348
|
-
context: {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
value: {
|
|
1353
|
-
src,
|
|
1354
|
-
alt,
|
|
1355
|
-
title: void 0
|
|
1356
|
-
},
|
|
1357
|
-
isInline: !1
|
|
1358
|
-
});
|
|
1359
|
-
if (!blockImageObject) {
|
|
1360
|
-
addSpan(``);
|
|
2029
|
+
case "em_close": {
|
|
2030
|
+
let decorator = consolidatedOptions.marks.em({ context: { schema: consolidatedOptions.schema } });
|
|
2031
|
+
if (!decorator) break;
|
|
2032
|
+
let index = markDefRefs.lastIndexOf(decorator);
|
|
2033
|
+
index !== -1 && markDefRefs.splice(index, 1);
|
|
1361
2034
|
break;
|
|
1362
2035
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
2036
|
+
case "s_open": {
|
|
2037
|
+
let decorator = consolidatedOptions.marks.strikeThrough({ context: { schema: consolidatedOptions.schema } });
|
|
2038
|
+
if (!decorator) {
|
|
2039
|
+
let strikeSnippet = truncateSnippet(collectInlineText(inlineChildren, childIndex, "s_open", "s_close"));
|
|
2040
|
+
report({
|
|
2041
|
+
type: "decorator-dropped",
|
|
2042
|
+
message: degradationMessage["decorator-dropped"]("strikeThrough"),
|
|
2043
|
+
line: inlineLine(),
|
|
2044
|
+
snippet: strikeSnippet
|
|
2045
|
+
});
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
markDefRefs.push(decorator);
|
|
1365
2049
|
break;
|
|
1366
2050
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
2051
|
+
case "s_close": {
|
|
2052
|
+
let decorator = consolidatedOptions.marks.strikeThrough({ context: { schema: consolidatedOptions.schema } });
|
|
2053
|
+
if (!decorator) break;
|
|
2054
|
+
let index = markDefRefs.lastIndexOf(decorator);
|
|
2055
|
+
index !== -1 && markDefRefs.splice(index, 1);
|
|
2056
|
+
break;
|
|
2057
|
+
}
|
|
2058
|
+
case "link_open": {
|
|
2059
|
+
let href = childToken.attrs?.find(([name]) => name === "href")?.at(1);
|
|
2060
|
+
if (!href) {
|
|
2061
|
+
let missingHrefSnippet = truncateSnippet(collectInlineText(inlineChildren, childIndex, "link_open", "link_close"));
|
|
2062
|
+
report({
|
|
2063
|
+
type: "annotation-dropped",
|
|
2064
|
+
message: degradationMessage["annotation-dropped"]("missing-url"),
|
|
2065
|
+
line: inlineLine(),
|
|
2066
|
+
snippet: missingHrefSnippet
|
|
2067
|
+
});
|
|
2068
|
+
break;
|
|
2069
|
+
}
|
|
2070
|
+
let title = childToken.attrs?.find(([name]) => name === "title")?.at(1), linkObject = consolidatedOptions.marks.link({
|
|
2071
|
+
context: {
|
|
2072
|
+
schema: consolidatedOptions.schema,
|
|
2073
|
+
keyGenerator: consolidatedOptions.keyGenerator
|
|
2074
|
+
},
|
|
2075
|
+
value: {
|
|
2076
|
+
href,
|
|
2077
|
+
title
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
if (!linkObject) {
|
|
2081
|
+
let linkSnippet = truncateSnippet(collectInlineText(inlineChildren, childIndex, "link_open", "link_close"));
|
|
2082
|
+
report({
|
|
2083
|
+
type: "annotation-dropped",
|
|
2084
|
+
message: degradationMessage["annotation-dropped"]("no-annotation"),
|
|
2085
|
+
line: inlineLine(),
|
|
2086
|
+
snippet: linkSnippet
|
|
2087
|
+
});
|
|
2088
|
+
break;
|
|
2089
|
+
}
|
|
2090
|
+
reportFieldsDropped(linkObject, inlineLine()), currentMarkDefs.push(linkObject), markDefRefs.push(linkObject._key);
|
|
2091
|
+
break;
|
|
2092
|
+
}
|
|
2093
|
+
case "link_close": {
|
|
2094
|
+
let markDefKeys = new Set(currentMarkDefs.map((d) => d._key)), lastLinkIndex;
|
|
2095
|
+
for (let markDefRef of markDefRefs.reverse()) if (markDefKeys.has(markDefRef)) {
|
|
2096
|
+
lastLinkIndex = markDefRefs.indexOf(markDefRef);
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
2099
|
+
if (lastLinkIndex !== void 0) {
|
|
2100
|
+
let realIndex = markDefRefs.length - 1 - lastLinkIndex;
|
|
2101
|
+
markDefRefs.splice(realIndex, 1);
|
|
2102
|
+
}
|
|
2103
|
+
break;
|
|
2104
|
+
}
|
|
2105
|
+
case "image": {
|
|
2106
|
+
let src = childToken.attrs?.find(([name]) => name === "src")?.at(1) || "", alt = unescapeImageAndLinkText(childToken.content || ""), inlineImageObject = consolidatedOptions.types.image({
|
|
2107
|
+
context: {
|
|
2108
|
+
schema: consolidatedOptions.schema,
|
|
2109
|
+
keyGenerator: consolidatedOptions.keyGenerator
|
|
2110
|
+
},
|
|
2111
|
+
value: {
|
|
2112
|
+
src,
|
|
2113
|
+
alt,
|
|
2114
|
+
title: void 0
|
|
2115
|
+
},
|
|
2116
|
+
isInline: !0
|
|
2117
|
+
});
|
|
2118
|
+
if (inlineImageObject) {
|
|
2119
|
+
if (reportFieldsDropped(inlineImageObject, inlineLine()), !currentBlock) {
|
|
2120
|
+
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
2121
|
+
style ? startBlock(style) : (reportStyleFallback("normal", inlineLine()), startBlock("normal"));
|
|
2122
|
+
}
|
|
2123
|
+
if (!currentBlock) throw Error("Expected current block after startBlock");
|
|
2124
|
+
currentBlock.children.push(inlineImageObject);
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
let blockImageObject = consolidatedOptions.types.image({
|
|
2128
|
+
context: {
|
|
2129
|
+
schema: consolidatedOptions.schema,
|
|
2130
|
+
keyGenerator: consolidatedOptions.keyGenerator
|
|
2131
|
+
},
|
|
2132
|
+
value: {
|
|
2133
|
+
src,
|
|
2134
|
+
alt,
|
|
2135
|
+
title: void 0
|
|
2136
|
+
},
|
|
2137
|
+
isInline: !1
|
|
2138
|
+
});
|
|
2139
|
+
if (!blockImageObject) {
|
|
2140
|
+
let inlineImageSnippet = truncateSnippet(alt || src);
|
|
2141
|
+
report({
|
|
2142
|
+
type: "image-to-text",
|
|
2143
|
+
message: degradationMessage["image-to-text"],
|
|
2144
|
+
line: inlineLine(),
|
|
2145
|
+
snippet: inlineImageSnippet
|
|
2146
|
+
}), addSpan(``);
|
|
2147
|
+
break;
|
|
2148
|
+
}
|
|
2149
|
+
if (inTableCell) {
|
|
2150
|
+
reportFieldsDropped(blockImageObject, inlineLine()), demotedTableImages.set(blockImageObject, "table-cell"), currentBlock && "children" in currentBlock && currentBlock.children.push(blockImageObject);
|
|
2151
|
+
break;
|
|
2152
|
+
}
|
|
2153
|
+
reportFieldsDropped(blockImageObject, inlineLine()), report({
|
|
2154
|
+
type: "image-inline-to-block",
|
|
2155
|
+
message: degradationMessage["image-inline-to-block"],
|
|
2156
|
+
line: inlineLine(),
|
|
2157
|
+
snippet: truncateSnippet(alt || src)
|
|
2158
|
+
}), flushBlock(), pushBlock(blockImageObject);
|
|
2159
|
+
let style = consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
2160
|
+
style && startBlock(style);
|
|
2161
|
+
break;
|
|
2162
|
+
}
|
|
2163
|
+
case "html_inline": if (consolidatedOptions.html.inline === "text") addSpan(childToken.content);
|
|
2164
|
+
else if (childToken.content) {
|
|
2165
|
+
let htmlInlineSnippet = truncateSnippet(childToken.content);
|
|
2166
|
+
report({
|
|
2167
|
+
type: "inline-html-dropped",
|
|
2168
|
+
message: degradationMessage["inline-html-dropped"],
|
|
2169
|
+
line: inlineLine(),
|
|
2170
|
+
snippet: htmlInlineSnippet
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
1371
2173
|
}
|
|
1372
|
-
case "html_inline": consolidatedOptions.html.inline === "text" && addSpan(childToken.content);
|
|
1373
2174
|
}
|
|
1374
2175
|
break;
|
|
1375
2176
|
}
|
|
1376
|
-
case "alert_open":
|
|
1377
|
-
flushBlock(), calloutStartTarget = blockTarget(), calloutStartIndex = calloutStartTarget.length, calloutType = token.markup,
|
|
2177
|
+
case "alert_open": {
|
|
2178
|
+
flushBlock(), calloutStartTarget = blockTarget(), calloutStartIndex = calloutStartTarget.length, calloutType = token.markup, calloutStartLine = lineOf(token);
|
|
2179
|
+
let blockquoteStyle = consolidatedOptions.block.blockquote({ context: { schema: consolidatedOptions.schema } });
|
|
2180
|
+
calloutPendingStyleFallbacks = [], blockquoteStyle || calloutPendingStyleFallbacks.push("blockquote");
|
|
2181
|
+
let style = blockquoteStyle ?? consolidatedOptions.block.normal({ context: { schema: consolidatedOptions.schema } });
|
|
2182
|
+
style || calloutPendingStyleFallbacks.push("normal"), currentBlockquoteStyle = style ?? "normal";
|
|
2183
|
+
break;
|
|
2184
|
+
}
|
|
2185
|
+
case "alert_title":
|
|
2186
|
+
calloutStartLine = lineOf(token);
|
|
1378
2187
|
break;
|
|
1379
|
-
case "alert_title": break;
|
|
1380
2188
|
case "alert_close":
|
|
1381
|
-
if (flushBlock(), calloutStartIndex !== null && calloutType !== null && calloutStartTarget !== null) {
|
|
2189
|
+
if (flushBlock(), calloutPendingStyleFallbacks = [], calloutStartIndex !== null && calloutType !== null && calloutStartTarget !== null) {
|
|
1382
2190
|
let contentBlocks = calloutStartTarget.splice(calloutStartIndex), calloutObject = consolidatedOptions.types.callout?.({
|
|
1383
2191
|
context: {
|
|
1384
2192
|
schema: consolidatedOptions.schema,
|
|
@@ -1390,13 +2198,41 @@ function markdownToPortableText(markdown, options) {
|
|
|
1390
2198
|
},
|
|
1391
2199
|
isInline: !1
|
|
1392
2200
|
});
|
|
1393
|
-
if (calloutObject) pushBlock(calloutObject);
|
|
1394
|
-
else
|
|
2201
|
+
if (calloutObject) reportFieldsDropped(calloutObject, calloutStartLine), pushBlock(calloutObject);
|
|
2202
|
+
else {
|
|
2203
|
+
report({
|
|
2204
|
+
type: "callout-fallback",
|
|
2205
|
+
message: degradationMessage["callout-fallback"](calloutType, currentBlockquoteStyle ?? "normal"),
|
|
2206
|
+
line: calloutStartLine
|
|
2207
|
+
});
|
|
2208
|
+
for (let block of contentBlocks) pushBlock(block);
|
|
2209
|
+
}
|
|
1395
2210
|
}
|
|
1396
|
-
calloutStartIndex = null, calloutStartTarget = null, calloutType = null, currentBlockquoteStyle = null;
|
|
2211
|
+
calloutStartIndex = null, calloutStartTarget = null, calloutType = null, calloutStartLine = void 0, currentBlockquoteStyle = null;
|
|
1397
2212
|
}
|
|
1398
2213
|
}
|
|
1399
|
-
return flushBlock(),
|
|
2214
|
+
return flushBlock(), degradationEvents.length > 0 && options?.onDegradation?.({
|
|
2215
|
+
degradations: degradationEvents,
|
|
2216
|
+
message: buildDegradationMessage(degradationEvents)
|
|
2217
|
+
}), portableText;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* A `json:object` fence always reconstructs its object, schema or no
|
|
2221
|
+
* schema: the fence carries its own `_type`, and degrading it to a code
|
|
2222
|
+
* block would reintroduce the loss the syntax exists to remove. Returns
|
|
2223
|
+
* `undefined` instead of throwing, so an unusable fence falls through to
|
|
2224
|
+
* the regular code path.
|
|
2225
|
+
*/
|
|
2226
|
+
function parseJsonObjectFence(code) {
|
|
2227
|
+
let parsed;
|
|
2228
|
+
try {
|
|
2229
|
+
parsed = JSON.parse(code);
|
|
2230
|
+
} catch {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (typeof parsed != "object" || !parsed || Array.isArray(parsed)) return;
|
|
2234
|
+
let objectValue = parsed;
|
|
2235
|
+
if (typeof objectValue._type == "string" && objectValue._type.length !== 0) return objectValue;
|
|
1400
2236
|
}
|
|
1401
2237
|
export { DefaultBlockSpacingRenderer, DefaultBlockquoteObjectRenderer, DefaultBlockquoteRenderer, DefaultCalloutRenderer, DefaultCodeBlockRenderer, DefaultCodeRenderer, DefaultEmRenderer, DefaultH1Renderer, DefaultH2Renderer, DefaultH3Renderer, DefaultH4Renderer, DefaultH5Renderer, DefaultH6Renderer, DefaultHardBreakRenderer, DefaultHorizontalRuleRenderer, DefaultHtmlRenderer, DefaultImageRenderer, DefaultLinkRenderer, DefaultListItemRenderer, DefaultListRenderer, DefaultNormalRenderer, DefaultStrikeThroughRenderer, DefaultStrongRenderer, DefaultTableRenderer, DefaultUnderlineRenderer, markdownToPortableText, portableTextToMarkdown };
|
|
1402
2238
|
|