@rathkit/rte-core 0.1.0-alpha.3 → 0.1.0-alpha.5
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 +1 -1
- package/dist/autoformat/autoformat.js +18 -3
- package/dist/base64.js +5 -1
- package/dist/dom/index.d.ts +1 -1
- package/dist/dom/render.js +26 -15
- package/dist/dom/view.d.ts +59 -6
- package/dist/dom/view.js +195 -38
- package/dist/edit.d.ts +6 -0
- package/dist/edit.js +101 -11
- package/dist/engine.d.ts +26 -4
- package/dist/engine.js +113 -23
- package/dist/export.d.ts +2 -2
- package/dist/export.js +22 -6
- package/dist/footnotes/index.d.ts +2 -0
- package/dist/footnotes/index.js +2 -0
- package/dist/footnotes/plugin.d.ts +6 -0
- package/dist/footnotes/plugin.js +130 -0
- package/dist/footnotes/reconcile.d.ts +4 -0
- package/dist/footnotes/reconcile.js +155 -0
- package/dist/html/parse.d.ts +8 -2
- package/dist/html/parse.js +6 -2
- package/dist/images/images.js +4 -3
- package/dist/images/index.d.ts +5 -1
- package/dist/images/index.js +3 -1
- package/dist/images/plugin.js +2 -1
- package/dist/{dom/imageview.d.ts → images/view.d.ts} +9 -6
- package/dist/{dom/imageview.js → images/view.js} +20 -14
- package/dist/import.d.ts +18 -2
- package/dist/import.js +87 -42
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/lists/lists.js +55 -30
- package/dist/markdown/detect.d.ts +1 -0
- package/dist/markdown/detect.js +81 -0
- package/dist/markdown/export.d.ts +10 -0
- package/dist/markdown/export.js +252 -0
- package/dist/markdown/index.d.ts +5 -0
- package/dist/markdown/index.js +4 -0
- package/dist/markdown/inline.d.ts +37 -0
- package/dist/markdown/inline.js +604 -0
- package/dist/markdown/parse.d.ts +4 -0
- package/dist/markdown/parse.js +586 -0
- package/dist/markdown/plugin.d.ts +20 -0
- package/dist/markdown/plugin.js +56 -0
- package/dist/model.d.ts +30 -2
- package/dist/model.js +2 -0
- package/dist/notes.d.ts +6 -0
- package/dist/notes.js +49 -0
- package/dist/ops.d.ts +6 -1
- package/dist/ops.js +5 -4
- package/dist/paste.js +9 -2
- package/dist/path.d.ts +10 -1
- package/dist/path.js +110 -11
- package/dist/plugin.d.ts +53 -2
- package/dist/policy.d.ts +41 -0
- package/dist/policy.js +40 -0
- package/dist/standard/index.d.ts +5 -3
- package/dist/standard/index.js +6 -5
- package/dist/stats.js +1 -1
- package/dist/svg/svg.js +9 -1
- package/dist/{dom/tableview.d.ts → tables/cells.d.ts} +7 -6
- package/dist/{dom/tableview.js → tables/cells.js} +16 -12
- package/dist/{dom/tablegrips.d.ts → tables/grips.d.ts} +5 -4
- package/dist/{dom/tablegrips.js → tables/grips.js} +10 -9
- package/dist/tables/index.d.ts +5 -1
- package/dist/tables/index.js +3 -1
- package/dist/tables/view.d.ts +2 -0
- package/dist/tables/view.js +15 -0
- package/dist/warning.d.ts +16 -0
- package/dist/warning.js +5 -0
- package/package.json +23 -1
package/dist/import.js
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import { decodeEntities } from "./html/entities.js";
|
|
2
2
|
import { parseHtml } from "./html/parse.js";
|
|
3
|
+
import { CONTENT_PROFILES, DATA_IMAGE_SIGNATURES, DEFAULT_LINK_SCHEMES, DROPPED_ELEMENTS, GROUPING_ELEMENTS, IMAGE_SCHEMES } from "./policy.js";
|
|
3
4
|
import { parseLine, SIDES, withSides } from "./cellborder.js";
|
|
4
|
-
import { COLOR_VALUE, COLUMN_WIDTH, MAX_INDENT, ROW_HEIGHT, inlineText, normalizeInline, } from "./model.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const CONTAINERS = new Set([
|
|
10
|
-
"div", "section", "article", "header", "footer", "aside", "nav", "figure", "figcaption", "address", "main", "form",
|
|
11
|
-
"body", "html", "center", "details", "summary", "dd", "dt", "fieldset", "hgroup",
|
|
12
|
-
]);
|
|
5
|
+
import { COLOR_VALUE, COLUMN_WIDTH, MAX_INDENT, NOTE_ID, ROW_HEIGHT, inlineText, normalizeInline, } from "./model.js";
|
|
6
|
+
import { asChildren } from "./path.js";
|
|
7
|
+
import { DEFAULT_MARKER } from "./model.js";
|
|
8
|
+
const DROP = DROPPED_ELEMENTS;
|
|
9
|
+
const CONTAINERS = GROUPING_ELEMENTS;
|
|
13
10
|
const MARK_TAGS = {
|
|
14
11
|
strong: "bold", b: "bold", em: "italic", i: "italic", cite: "italic", dfn: "italic", var: "italic", u: "underline", ins: "underline",
|
|
15
12
|
s: "strike", strike: "strike", del: "strike", code: "code", tt: "code", kbd: "code", samp: "code", sup: "sup", sub: "sub",
|
|
16
13
|
};
|
|
17
|
-
const RASTER = [
|
|
18
|
-
["image/png", "iVBORw0KGgo"], ["image/jpeg", "/9j/"], ["image/gif", "R0lGOD"], ["image/webp", "UklGR"], ["image/bmp", "Qk"],
|
|
19
|
-
];
|
|
20
14
|
const COLOR = COLOR_VALUE;
|
|
21
15
|
export function parseStyle(style) {
|
|
22
16
|
const out = new Map();
|
|
@@ -76,9 +70,11 @@ function safeImageSrc(raw, settings) {
|
|
|
76
70
|
if (!match)
|
|
77
71
|
return undefined;
|
|
78
72
|
const body = match[2];
|
|
79
|
-
return
|
|
73
|
+
return DATA_IMAGE_SIGNATURES.some(([, magic]) => body.startsWith(magic)) ? value : undefined;
|
|
80
74
|
}
|
|
81
|
-
|
|
75
|
+
if (!settings.relative && !/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(value.replace(INVISIBLE, "")))
|
|
76
|
+
return undefined;
|
|
77
|
+
return safeUrl(value, new Set(IMAGE_SCHEMES));
|
|
82
78
|
}
|
|
83
79
|
class Sink {
|
|
84
80
|
blocks = [];
|
|
@@ -275,6 +271,12 @@ function inlineOf(nodes, ctx, settings) {
|
|
|
275
271
|
content.push(...(content.length > 0 ? [{ type: "br" }] : []), ...block.content);
|
|
276
272
|
return normalizeInline(content);
|
|
277
273
|
}
|
|
274
|
+
const NOTE_PREFIX = /^(?:user-content-)?(?:fnref|fn)[-:_]?/i;
|
|
275
|
+
const noteId = (raw, strip) => {
|
|
276
|
+
const id = strip ? raw?.replace(/^#/, "").replace(NOTE_PREFIX, "").replace(/[^\w-]/g, "") : raw;
|
|
277
|
+
return id !== undefined && NOTE_ID.test(id) ? id : undefined;
|
|
278
|
+
};
|
|
279
|
+
const hasClass = (el, name) => (el.attrs.class ?? "").split(/\s+/).includes(name);
|
|
278
280
|
function convertElement(el, sink, ctx, settings) {
|
|
279
281
|
const tag = el.tag;
|
|
280
282
|
if (DROP.has(tag) || tag === "title")
|
|
@@ -291,7 +293,7 @@ function convertElement(el, sink, ctx, settings) {
|
|
|
291
293
|
type: listItem.type,
|
|
292
294
|
...(listItem.start !== undefined && listItem.start !== 1 ? { start: listItem.start } : {}),
|
|
293
295
|
...(listItem.style !== undefined ? { style: listItem.style } : {}),
|
|
294
|
-
items: [{ content: normalizeInline(listItem.content),
|
|
296
|
+
items: [{ content: normalizeInline(listItem.content), children: [], level: listItem.level, listId: listItem.listId }],
|
|
295
297
|
});
|
|
296
298
|
}
|
|
297
299
|
return;
|
|
@@ -304,6 +306,22 @@ function convertElement(el, sink, ctx, settings) {
|
|
|
304
306
|
sink.flush((content) => ({ type: "h", level, content: withoutBold(content) }), true);
|
|
305
307
|
return;
|
|
306
308
|
}
|
|
309
|
+
if (tag === "a" && (el.attrs.role === "doc-backlink" || el.attrs["data-footnote-backref"] !== undefined || hasClass(el, "footnote-back") || hasClass(el, "footnote-backref")))
|
|
310
|
+
return;
|
|
311
|
+
if ((tag === "sup" && el.attrs["data-note"] !== undefined) || (tag === "a" && (el.attrs.role === "doc-noteref" || el.attrs["data-footnote-ref"] !== undefined || hasClass(el, "footnote-ref")))) {
|
|
312
|
+
const id = tag === "sup" ? noteId(el.attrs["data-note"], false) : noteId(el.attrs.href, true);
|
|
313
|
+
if (id !== undefined) {
|
|
314
|
+
sink.push({ type: "note", id });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (el.attrs.role === "doc-endnotes" || el.attrs["data-footnotes"] !== undefined || hasClass(el, "footnotes")) {
|
|
319
|
+
sink.flush();
|
|
320
|
+
for (const ol of el.tag === "ol" ? [el] : el.children)
|
|
321
|
+
if (ol.type === "el" && ol.tag === "ol")
|
|
322
|
+
sink.blocks.push(convertList(ol, ctx, settings, true));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
307
325
|
switch (tag) {
|
|
308
326
|
case "br":
|
|
309
327
|
sink.push({ type: "br" });
|
|
@@ -452,18 +470,17 @@ function listStyleOf(el) {
|
|
|
452
470
|
return style && /^[a-z-]+$/.test(style) && !/^(initial|inherit|unset|revert|none)$/.test(style) ? style : undefined;
|
|
453
471
|
}
|
|
454
472
|
const isCheckbox = (n) => n.type === "el" && n.tag === "input" && (n.attrs.type ?? "").toLowerCase() === "checkbox";
|
|
455
|
-
const blankText = (n) => n.type === "text" && n.text.trim() === "";
|
|
456
473
|
function taskOf(li) {
|
|
457
474
|
const own = (li.attrs["aria-checked"] ?? li.attrs["data-checked"] ?? "").toLowerCase();
|
|
458
475
|
const classes = (li.attrs.class ?? "").toLowerCase().split(/\s+/);
|
|
459
476
|
const fromMarkup = own === "true" || classes.includes("checked") || classes.includes("task-done") ? true : own === "false" || classes.includes("unchecked") ? false : undefined;
|
|
460
477
|
const kids = li.children;
|
|
461
|
-
const at = kids.findIndex((n) => !
|
|
478
|
+
const at = kids.findIndex((n) => !isBlank(n));
|
|
462
479
|
const first = at === -1 ? undefined : kids[at];
|
|
463
480
|
if (first && isCheckbox(first))
|
|
464
481
|
return { checked: "checked" in first.attrs, children: [...kids.slice(0, at), ...kids.slice(at + 1)] };
|
|
465
482
|
if (first && first.type === "el" && /^(label|p|span|div)$/.test(first.tag)) {
|
|
466
|
-
const inner = first.children.findIndex((n) => !
|
|
483
|
+
const inner = first.children.findIndex((n) => !isBlank(n));
|
|
467
484
|
const box = inner === -1 ? undefined : first.children[inner];
|
|
468
485
|
if (box && isCheckbox(box)) {
|
|
469
486
|
const wrapper = { ...first, children: [...first.children.slice(0, inner), ...first.children.slice(inner + 1)] };
|
|
@@ -472,8 +489,10 @@ function taskOf(li) {
|
|
|
472
489
|
}
|
|
473
490
|
return fromMarkup === undefined ? undefined : { checked: fromMarkup, children: kids };
|
|
474
491
|
}
|
|
475
|
-
function convertList(el, ctx, settings) {
|
|
492
|
+
function convertList(el, ctx, settings, notes = false) {
|
|
476
493
|
const list = { type: el.tag === "ol" ? "ol" : "ul", items: [] };
|
|
494
|
+
if (notes)
|
|
495
|
+
list.notes = true;
|
|
477
496
|
const start = el.tag === "ol" && el.attrs.start !== undefined ? Number.parseInt(el.attrs.start, 10) : NaN;
|
|
478
497
|
if (Number.isFinite(start) && start !== 1 && start >= 0 && start < 100000)
|
|
479
498
|
list.start = start;
|
|
@@ -483,8 +502,8 @@ function convertList(el, ctx, settings) {
|
|
|
483
502
|
continue;
|
|
484
503
|
if (child.tag === "ul" || child.tag === "ol") {
|
|
485
504
|
const nested = convertList(child, ctx, settings);
|
|
486
|
-
const last = list.items[list.items.length - 1] ?? list.items[list.items.push({ content: [],
|
|
487
|
-
last.
|
|
505
|
+
const last = list.items[list.items.length - 1] ?? list.items[list.items.push({ content: [], children: [], bare: true }) - 1];
|
|
506
|
+
last.children.push(nested);
|
|
488
507
|
continue;
|
|
489
508
|
}
|
|
490
509
|
if (child.tag !== "li")
|
|
@@ -497,19 +516,34 @@ function convertList(el, ctx, settings) {
|
|
|
497
516
|
const task = taskOf(child);
|
|
498
517
|
convertNodes(task ? task.children : child.children, inner, ctx, settings);
|
|
499
518
|
inner.flush();
|
|
500
|
-
const item = { content: [],
|
|
519
|
+
const item = { content: [], children: [] };
|
|
501
520
|
if (task)
|
|
502
521
|
item.checked = task.checked;
|
|
522
|
+
let leading = true;
|
|
503
523
|
for (const block of inner.blocks) {
|
|
504
|
-
if (block.type === "
|
|
505
|
-
item.sublists.push(block);
|
|
506
|
-
else if (block.type === "p" || block.type === "h") {
|
|
524
|
+
if (leading && (block.type === "p" || block.type === "h")) {
|
|
507
525
|
if (item.content.length > 0)
|
|
508
526
|
item.content.push({ type: "br", paragraph: true });
|
|
509
527
|
item.content.push(...block.content);
|
|
528
|
+
continue;
|
|
510
529
|
}
|
|
530
|
+
leading = false;
|
|
531
|
+
if (isList(block))
|
|
532
|
+
item.children.push(block);
|
|
533
|
+
else
|
|
534
|
+
item.children.push(...asChildren([block]));
|
|
511
535
|
}
|
|
512
536
|
item.content = normalizeInline(item.content);
|
|
537
|
+
if (notes) {
|
|
538
|
+
const tail = item.content[item.content.length - 1];
|
|
539
|
+
if (tail?.type === "text")
|
|
540
|
+
item.content = normalizeInline([...item.content.slice(0, -1), { ...tail, text: tail.text.trimEnd() }]);
|
|
541
|
+
const id = noteId(child.attrs["data-note"], false) ?? noteId(child.attrs.id, true);
|
|
542
|
+
if (id !== undefined)
|
|
543
|
+
item.id = id;
|
|
544
|
+
}
|
|
545
|
+
if (item.content.length === 0 && !task && item.children.length > 0 && /list-style(-type)?\s*:\s*none/i.test(child.attrs.style ?? ""))
|
|
546
|
+
item.bare = true;
|
|
513
547
|
const level = levelOf(child.attrs["data-aria-level"] ?? child.attrs["aria-level"]);
|
|
514
548
|
if (level !== undefined)
|
|
515
549
|
item.level = level;
|
|
@@ -637,7 +671,7 @@ function rowHeights(rowEls, kept) {
|
|
|
637
671
|
return heights.some((h) => h !== null) ? heights : undefined;
|
|
638
672
|
}
|
|
639
673
|
function columnWidths(table, rows) {
|
|
640
|
-
const cols =
|
|
674
|
+
const cols = rows.reduce((most, row) => Math.max(most, row.reduce((sum, cell) => sum + cell.colspan, 0)), 0);
|
|
641
675
|
const widths = [];
|
|
642
676
|
const visit = (nodes) => {
|
|
643
677
|
for (const n of nodes) {
|
|
@@ -658,7 +692,7 @@ function columnWidths(table, rows) {
|
|
|
658
692
|
if (firstRow)
|
|
659
693
|
for (const cell of firstRow.children)
|
|
660
694
|
if (cell.type === "el" && (cell.tag === "td" || cell.tag === "th")) {
|
|
661
|
-
const span = Math.max(Number.parseInt(cell.attrs.colspan ?? "1", 10) || 1, 1);
|
|
695
|
+
const span = Math.min(Math.max(Number.parseInt(cell.attrs.colspan ?? "1", 10) || 1, 1), 100);
|
|
662
696
|
if (span === 1)
|
|
663
697
|
widths.push(widthOf(cell));
|
|
664
698
|
else
|
|
@@ -693,13 +727,12 @@ function padRows(rows) {
|
|
|
693
727
|
reach[k] = reach[k] - 1;
|
|
694
728
|
return used;
|
|
695
729
|
});
|
|
696
|
-
const widest = Math.max(
|
|
730
|
+
const widest = widths.reduce((most, w) => Math.max(most, w), 0);
|
|
697
731
|
return rows.map((cells, i) => {
|
|
698
732
|
const missing = widest - widths[i];
|
|
699
733
|
return missing > 0 ? [...cells, ...Array.from({ length: missing }, () => ({ header: false, colspan: 1, rowspan: 1, blocks: [{ type: "p", content: [] }] }))] : cells;
|
|
700
734
|
});
|
|
701
735
|
}
|
|
702
|
-
const DEFAULT_MARKER = (type, depth) => (type === "ol" ? "decimal" : ["disc", "circle", "square"][Math.min(depth, 2)]);
|
|
703
736
|
const isList = (block) => block.type === "ul" || block.type === "ol";
|
|
704
737
|
const hinted = (list) => list.items.length > 0 && list.items.every((item) => item.level !== undefined);
|
|
705
738
|
function finishBlocks(blocks) {
|
|
@@ -744,8 +777,8 @@ function nestByLevel(lists) {
|
|
|
744
777
|
const fresh = { type: list.type, items: [] };
|
|
745
778
|
const parent = stack[stack.length - 1];
|
|
746
779
|
if (parent) {
|
|
747
|
-
const host = parent.items[parent.items.length - 1] ?? parent.items[parent.items.push({ content: [],
|
|
748
|
-
host.
|
|
780
|
+
const host = parent.items[parent.items.length - 1] ?? parent.items[parent.items.push({ content: [], children: [], bare: true }) - 1];
|
|
781
|
+
host.children.push(fresh);
|
|
749
782
|
}
|
|
750
783
|
else {
|
|
751
784
|
result.push(fresh);
|
|
@@ -757,15 +790,15 @@ function nestByLevel(lists) {
|
|
|
757
790
|
stack.push(fresh);
|
|
758
791
|
ids.push(item.listId);
|
|
759
792
|
}
|
|
760
|
-
stack[stack.length - 1].items.push({ content: item.content,
|
|
793
|
+
stack[stack.length - 1].items.push({ content: item.content, children: item.children, ...(item.checked !== undefined ? { checked: item.checked } : {}) });
|
|
761
794
|
}
|
|
762
795
|
}
|
|
763
796
|
return result.map((l) => freezeList(l, 0));
|
|
764
797
|
}
|
|
765
798
|
function freezeList(list, depth) {
|
|
766
|
-
const items = list.items.map((item) => ({ content: item.content,
|
|
799
|
+
const items = list.items.map((item) => ({ content: item.content, children: item.children.map((child) => (child.type === "ul" || child.type === "ol" ? freezeList(child, depth + 1) : child)), ...(item.checked !== undefined ? { checked: item.checked } : {}), ...(item.id !== undefined ? { id: item.id } : {}), ...(item.bare ? { bare: true } : {}) }));
|
|
767
800
|
const style = list.style !== undefined && list.style !== DEFAULT_MARKER(list.type, depth) ? list.style : undefined;
|
|
768
|
-
return { type: list.type, ...(list.start !== undefined ? { start: list.start } : {}), ...(style !== undefined ? { style } : {}), items };
|
|
801
|
+
return { type: list.type, ...(list.notes ? { notes: true } : {}), ...(list.start !== undefined ? { start: list.start } : {}), ...(style !== undefined ? { style } : {}), items };
|
|
769
802
|
}
|
|
770
803
|
function repairRewrittenSpaces(nodes) {
|
|
771
804
|
return nodes.map((node) => {
|
|
@@ -784,17 +817,29 @@ function repairRewrittenSpaces(nodes) {
|
|
|
784
817
|
export function importBlocks(html, options = {}) {
|
|
785
818
|
const settings = {
|
|
786
819
|
allowStyles: (options.styles ?? "allowlist") === "allowlist",
|
|
787
|
-
schemes: new Set(options.linkSchemes ??
|
|
820
|
+
schemes: new Set(options.linkSchemes ?? DEFAULT_LINK_SCHEMES),
|
|
788
821
|
keepEmpty: options.keepEmptyBlocks ?? false,
|
|
789
|
-
maxData: options.maxDataImageLength ??
|
|
822
|
+
maxData: options.maxDataImageLength ?? CONTENT_PROFILES.standard.maxDataImageLength,
|
|
823
|
+
relative: options.relativeImages ?? true,
|
|
790
824
|
svg: options.sanitizeSvg,
|
|
791
825
|
recognizers: options.recognizers ?? [],
|
|
792
826
|
};
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
827
|
+
const maxInput = options.maxInputLength ?? CONTENT_PROFILES.standard.maxInputLength;
|
|
828
|
+
if (html.length > maxInput) {
|
|
829
|
+
options.onLimit?.({ limit: "maxInputLength", max: maxInput, length: html.length });
|
|
830
|
+
return [];
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
const sink = new Sink(settings);
|
|
834
|
+
const tree = parseHtml(html, options.maxDepth ?? CONTENT_PROFILES.standard.maxDepth);
|
|
835
|
+
convertNodes(/-webkit-text-stroke-width/.test(html) ? repairRewrittenSpaces(tree) : tree, sink, { marks: [], inLink: false, pre: false }, settings);
|
|
836
|
+
sink.flush();
|
|
837
|
+
return finishBlocks(sink.blocks);
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
options.onError?.(error);
|
|
841
|
+
return [];
|
|
842
|
+
}
|
|
798
843
|
}
|
|
799
844
|
export function importHtml(html, options = {}) {
|
|
800
845
|
const blocks = importBlocks(html, { keepEmptyBlocks: true, ...options });
|
|
@@ -817,4 +862,4 @@ export function importText(value) {
|
|
|
817
862
|
}
|
|
818
863
|
const decodeLineBreaks = (value) => value.replace(/\r\n?|\u2028|\u2029/g, "\n").replace(/^\n+|\n+$/g, "");
|
|
819
864
|
export { decodeEntities };
|
|
820
|
-
export const checkImageSrc = (src, maxDataLength = 12_000_000, sanitizeSvg) => safeImageSrc(src, { maxData: maxDataLength, svg: sanitizeSvg });
|
|
865
|
+
export const checkImageSrc = (src, maxDataLength = 12_000_000, sanitizeSvg, relative = true) => safeImageSrc(src, { maxData: maxDataLength, svg: sanitizeSvg, relative });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
export declare const PACKAGE_NAME: "@rathkit/rte-core";
|
|
2
1
|
export * from "./model.ts";
|
|
3
2
|
export { checkImageSrc, importBlocks, importHtml, importText, safeUrl, parseStyle, type ImportOptions } from "./import.ts";
|
|
3
|
+
export { WARNING_CODES, type Warning, type WarningCode } from "./warning.ts";
|
|
4
|
+
export { CONTENT_PROFILES, contentPolicy, type ContentLimits, type ContentOptions, type ContentPolicy, type ContentProfile } from "./policy.ts";
|
|
4
5
|
export { exportBlock, exportHtml, exportInline } from "./export.ts";
|
|
5
6
|
export * from "./path.ts";
|
|
6
7
|
export * from "./edit.ts";
|
|
8
|
+
export type { CopyFormatter, Normalizer } from "./plugin.ts";
|
|
9
|
+
export { isNotes, noteIds } from "./notes.ts";
|
|
7
10
|
export { defaultPaste, extractFragment, pasteBlocks, type ClipboardData, type PasteHandler } from "./paste.ts";
|
|
8
11
|
export { decodeBase64, encodeBase64 } from "./base64.ts";
|
|
9
12
|
export { createEngine } from "./core.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export const PACKAGE_NAME = "@rathkit/rte-core";
|
|
2
1
|
export * from "./model.js";
|
|
3
2
|
export { checkImageSrc, importBlocks, importHtml, importText, safeUrl, parseStyle } from "./import.js";
|
|
3
|
+
export { WARNING_CODES } from "./warning.js";
|
|
4
|
+
export { CONTENT_PROFILES, contentPolicy } from "./policy.js";
|
|
4
5
|
export { exportBlock, exportHtml, exportInline } from "./export.js";
|
|
5
6
|
export * from "./path.js";
|
|
6
7
|
export * from "./edit.js";
|
|
8
|
+
export { isNotes, noteIds } from "./notes.js";
|
|
7
9
|
export { defaultPaste, extractFragment, pasteBlocks } from "./paste.js";
|
|
8
10
|
export { decodeBase64, encodeBase64 } from "./base64.js";
|
|
9
11
|
export { createEngine } from "./core.js";
|
package/dist/lists/lists.js
CHANGED
|
@@ -2,13 +2,28 @@ import { deleteSelection, sliceInline } from "../edit.js";
|
|
|
2
2
|
import { inlineText, normalizeInline } from "../model.js";
|
|
3
3
|
import { caret, clampPos, containerPaths, selectedContainers, fixup, getBlock, getContent, isCollapsed, kindAt, mapBlockList, pathKey, setContent, } from "../path.js";
|
|
4
4
|
function locateItem(doc, path) {
|
|
5
|
+
const inList = (list, rest, listBlock, steps) => {
|
|
6
|
+
const [i, k, ...more] = rest;
|
|
7
|
+
if (i === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (k === undefined)
|
|
10
|
+
return { listBlock, item: [...steps, i] };
|
|
11
|
+
const child = list.items[i]?.children[k];
|
|
12
|
+
if (!child)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (child.type === "ul" || child.type === "ol")
|
|
15
|
+
return inList(child, more, listBlock, [...steps, i, k]);
|
|
16
|
+
if (child.type === "quote")
|
|
17
|
+
return walk(child.blocks, more, [...listBlock, ...steps, i, k]);
|
|
18
|
+
return undefined;
|
|
19
|
+
};
|
|
5
20
|
const walk = (blocks, rest, prefix) => {
|
|
6
21
|
const [n, ...more] = rest;
|
|
7
22
|
const block = n === undefined ? undefined : blocks[n];
|
|
8
23
|
if (!block)
|
|
9
24
|
return undefined;
|
|
10
25
|
if (block.type === "ul" || block.type === "ol")
|
|
11
|
-
return
|
|
26
|
+
return inList(block, more, [...prefix, n], []);
|
|
12
27
|
if (block.type === "quote")
|
|
13
28
|
return walk(block.blocks, more, [...prefix, n]);
|
|
14
29
|
if (block.type === "table") {
|
|
@@ -30,8 +45,8 @@ function listOfItem(doc, path) {
|
|
|
30
45
|
for (let at = 0;; at += 2) {
|
|
31
46
|
if (steps.length - at === 1)
|
|
32
47
|
return { list, index: steps[at] };
|
|
33
|
-
const sub = list.items[steps[at]]?.
|
|
34
|
-
if (
|
|
48
|
+
const sub = list.items[steps[at]]?.children[steps[at + 1]];
|
|
49
|
+
if (sub?.type !== "ul" && sub?.type !== "ol")
|
|
35
50
|
return undefined;
|
|
36
51
|
list = sub;
|
|
37
52
|
}
|
|
@@ -53,21 +68,21 @@ function splitList(list, path, liftChildren) {
|
|
|
53
68
|
const item = list.items[i];
|
|
54
69
|
const numbering = (count) => (list.type === "ol" ? { start: (list.start ?? 1) + count } : {});
|
|
55
70
|
if (k === undefined) {
|
|
56
|
-
const placeholder = item.
|
|
71
|
+
const placeholder = item.children.length > 0 && !liftChildren ? [{ content: [], children: item.children, bare: true }] : [];
|
|
57
72
|
const rest = [...placeholder, ...list.items.slice(i + 1)];
|
|
58
73
|
return {
|
|
59
74
|
before: i > 0 ? { ...list, items: list.items.slice(0, i) } : undefined,
|
|
60
75
|
content: item.content,
|
|
61
|
-
lifted: liftChildren ? item.
|
|
76
|
+
lifted: liftChildren ? item.children : [],
|
|
62
77
|
after: rest.length > 0 ? { ...list, ...numbering(i + 1), items: rest } : undefined,
|
|
63
78
|
};
|
|
64
79
|
}
|
|
65
|
-
const inner = splitList(item.
|
|
66
|
-
const keptSublists = [...item.
|
|
67
|
-
const afterSublists = [...(inner.after ? [inner.after] : []), ...item.
|
|
68
|
-
const rest = [...(afterSublists.length > 0 ? [{ content: [],
|
|
80
|
+
const inner = splitList(item.children[k], path.slice(2), liftChildren);
|
|
81
|
+
const keptSublists = [...item.children.slice(0, k), ...(inner.before ? [inner.before] : [])];
|
|
82
|
+
const afterSublists = [...(inner.after ? [inner.after] : []), ...item.children.slice(k + 1)];
|
|
83
|
+
const rest = [...(afterSublists.length > 0 ? [{ content: [], children: afterSublists, bare: true }] : []), ...list.items.slice(i + 1)];
|
|
69
84
|
return {
|
|
70
|
-
before: { ...list, items: [...list.items.slice(0, i), { content: item.content,
|
|
85
|
+
before: { ...list, items: [...list.items.slice(0, i), { content: item.content, children: keptSublists }] },
|
|
71
86
|
content: inner.content,
|
|
72
87
|
lifted: inner.lifted,
|
|
73
88
|
after: rest.length > 0 ? { ...list, ...numbering(i + 1), items: rest } : undefined,
|
|
@@ -98,7 +113,7 @@ function updateListOf(doc, itemPath, change) {
|
|
|
98
113
|
if (steps.length === 1)
|
|
99
114
|
return change(list);
|
|
100
115
|
const [i, k] = steps;
|
|
101
|
-
return { ...list, items: list.items.map((item, idx) => (idx !== i ? item : { ...item,
|
|
116
|
+
return { ...list, items: list.items.map((item, idx) => (idx !== i ? item : { ...item, children: item.children.map((sub, s) => (s === k && (sub.type === "ul" || sub.type === "ol") ? edit(sub, steps.slice(2)) : sub)) })) };
|
|
102
117
|
};
|
|
103
118
|
return replaceBlock(doc, loc.listBlock, [edit(root, loc.item)]);
|
|
104
119
|
}
|
|
@@ -179,7 +194,7 @@ export function toggleList(doc, sel, type, options = {}) {
|
|
|
179
194
|
for (const run of [...runs].reverse()) {
|
|
180
195
|
const first = run[0];
|
|
181
196
|
next = fixup(mapBlockList(next, first, (blocks, index) => {
|
|
182
|
-
const items = run.map((_, n) => ({ content: blocks[index + n].content,
|
|
197
|
+
const items = run.map((_, n) => ({ content: blocks[index + n].content, children: [] }));
|
|
183
198
|
let list = withOptions({ type, items }, options);
|
|
184
199
|
let from = index;
|
|
185
200
|
let to = index + run.length;
|
|
@@ -218,7 +233,7 @@ function mapListAt(doc, itemPath, fn) {
|
|
|
218
233
|
const [i, k] = steps;
|
|
219
234
|
return {
|
|
220
235
|
...list,
|
|
221
|
-
items: list.items.map((item, idx) => idx !== i ? item : { ...item,
|
|
236
|
+
items: list.items.map((item, idx) => idx !== i ? item : { ...item, children: item.children.flatMap((sub, s) => (s !== k || (sub.type !== "ul" && sub.type !== "ol") ? [sub] : [edit(sub, steps.slice(2))].filter((x) => x !== undefined))) }),
|
|
222
237
|
};
|
|
223
238
|
};
|
|
224
239
|
const next = edit(root, loc.item);
|
|
@@ -247,16 +262,16 @@ export function indentItems(doc, sel, maxDepth = Number.POSITIVE_INFINITY) {
|
|
|
247
262
|
const flat = moved;
|
|
248
263
|
const previous = list.items[index - 1];
|
|
249
264
|
const nested = (target) => {
|
|
250
|
-
const last = target.
|
|
251
|
-
const
|
|
252
|
-
? [...target.
|
|
253
|
-
: [...target.
|
|
254
|
-
return { ...target,
|
|
265
|
+
const last = target.children.at(-1);
|
|
266
|
+
const children = last && last.type === list.type
|
|
267
|
+
? [...target.children.slice(0, -1), { ...last, items: [...last.items, ...flat] }]
|
|
268
|
+
: [...target.children, { type: list.type, items: flat }];
|
|
269
|
+
return { ...target, children };
|
|
255
270
|
};
|
|
256
271
|
const rest = list.items.slice(index + group.length);
|
|
257
272
|
if (previous)
|
|
258
273
|
return { ...list, items: [...list.items.slice(0, index - 1), nested(previous), ...rest] };
|
|
259
|
-
return { ...list, items: [nested({ content: [],
|
|
274
|
+
return { ...list, items: [nested({ content: [], children: [], bare: true }), ...rest] };
|
|
260
275
|
});
|
|
261
276
|
}
|
|
262
277
|
return { doc: next, selection: carry(doc, next, sel) };
|
|
@@ -271,6 +286,10 @@ export function outdentItems(doc, sel) {
|
|
|
271
286
|
if (!loc)
|
|
272
287
|
continue;
|
|
273
288
|
if (loc.item.length === 1) {
|
|
289
|
+
const here = listOfItem(next, path);
|
|
290
|
+
const held = here?.list.items[here.index];
|
|
291
|
+
if (held && held.content.length === 0 && held.children.length > 0)
|
|
292
|
+
continue;
|
|
274
293
|
next = takeOutOfList(next, path, true);
|
|
275
294
|
continue;
|
|
276
295
|
}
|
|
@@ -279,16 +298,17 @@ export function outdentItems(doc, sel) {
|
|
|
279
298
|
const itemIndex = path.at(-1);
|
|
280
299
|
next = mapListAt(next, parentPath, (list, parentIndex) => {
|
|
281
300
|
const parent = list.items[parentIndex];
|
|
282
|
-
const sub = parent.
|
|
301
|
+
const sub = parent.children[sublistIndex];
|
|
283
302
|
const moved = sub.items[itemIndex];
|
|
284
303
|
const followers = sub.items.slice(itemIndex + 1);
|
|
285
304
|
const keptSub = itemIndex > 0 ? [{ ...sub, items: sub.items.slice(0, itemIndex) }] : [];
|
|
286
305
|
const lifted = {
|
|
287
306
|
content: moved.content,
|
|
288
|
-
|
|
307
|
+
...(moved.bare ? { bare: true } : {}),
|
|
308
|
+
children: [...moved.children, ...(followers.length > 0 ? [{ type: sub.type, ...(sub.style !== undefined ? { style: sub.style } : {}), items: followers }] : [])],
|
|
289
309
|
};
|
|
290
|
-
const
|
|
291
|
-
const items = [...list.items.slice(0, parentIndex), { content: parent.content,
|
|
310
|
+
const children = [...parent.children.slice(0, sublistIndex), ...keptSub, ...parent.children.slice(sublistIndex + 1)];
|
|
311
|
+
const items = [...list.items.slice(0, parentIndex), { content: parent.content, children }, lifted, ...list.items.slice(parentIndex + 1)];
|
|
292
312
|
return { ...list, items };
|
|
293
313
|
});
|
|
294
314
|
}
|
|
@@ -340,6 +360,11 @@ export function backspaceAtItemStart(doc, pos) {
|
|
|
340
360
|
const held = listOfItem(doc, pos.path);
|
|
341
361
|
if (held?.list.items[held.index]?.checked !== undefined)
|
|
342
362
|
return { doc: setChecked(doc, pos.path, undefined), selection: caret(pos) };
|
|
363
|
+
const only = held?.list.items[held.index];
|
|
364
|
+
if (only && only.content.length === 0 && only.bare !== true && (only.children[0]?.type === "ul" || only.children[0]?.type === "ol")) {
|
|
365
|
+
const next = mapListAt(doc, pos.path, (list, index) => ({ ...list, items: list.items.map((item, i) => (i === index ? { ...item, bare: true } : item)) }));
|
|
366
|
+
return { doc: next, selection: caret(pos) };
|
|
367
|
+
}
|
|
343
368
|
if (!held || held.index !== 0)
|
|
344
369
|
return undefined;
|
|
345
370
|
return leaveListItem(doc, pos);
|
|
@@ -351,7 +376,7 @@ export function splitListItem(doc, pos) {
|
|
|
351
376
|
const item = held?.list.items[held.index];
|
|
352
377
|
if (!loc || !item)
|
|
353
378
|
return undefined;
|
|
354
|
-
if (content.length === 0 && item.
|
|
379
|
+
if (content.length === 0 && item.children.length === 0)
|
|
355
380
|
return leaveListItem(doc, pos);
|
|
356
381
|
const before = sliceInline(content, 0, pos.offset);
|
|
357
382
|
const after = sliceInline(content, pos.offset);
|
|
@@ -359,8 +384,8 @@ export function splitListItem(doc, pos) {
|
|
|
359
384
|
const next = mapListAt(doc, pos.path, (list, index) => ({
|
|
360
385
|
...list,
|
|
361
386
|
items: list.items.flatMap((candidate, i) => (i !== index ? [candidate] : [
|
|
362
|
-
{ content: before,
|
|
363
|
-
{ content: after,
|
|
387
|
+
{ content: before, children: [], ...(item.checked !== undefined ? { checked: item.checked } : {}), ...(item.id !== undefined ? { id: item.id } : {}) },
|
|
388
|
+
{ content: after, children: item.children, ...(item.checked !== undefined ? { checked: false } : {}), ...(item.bare && after.length === 0 ? { bare: true } : {}) },
|
|
364
389
|
])),
|
|
365
390
|
}));
|
|
366
391
|
return { doc: next, selection: caret({ path: containerPaths(next)[ordinal + 1] ?? pos.path, offset: 0 }) };
|
|
@@ -377,11 +402,11 @@ export function insertIntoList(doc, pos, blocks, before, after) {
|
|
|
377
402
|
if (!item)
|
|
378
403
|
return list;
|
|
379
404
|
const rows = others.length === 0
|
|
380
|
-
? [{ content: normalizeInline([...before, ...first, ...after])
|
|
405
|
+
? [{ ...item, content: normalizeInline([...before, ...first, ...after]) }]
|
|
381
406
|
: [
|
|
382
|
-
{ content: normalizeInline([...before, ...first]),
|
|
383
|
-
...others.slice(0, -1).map((content) => ({ content,
|
|
384
|
-
{ content: normalizeInline([...lastText, ...after]),
|
|
407
|
+
{ content: normalizeInline([...before, ...first]), children: [], ...(item.checked !== undefined ? { checked: item.checked } : {}), ...(item.id !== undefined ? { id: item.id } : {}) },
|
|
408
|
+
...others.slice(0, -1).map((content) => ({ content, children: [], ...(item.checked !== undefined ? { checked: false } : {}) })),
|
|
409
|
+
{ content: normalizeInline([...lastText, ...after]), children: item.children, ...(item.checked !== undefined ? { checked: false } : {}) },
|
|
385
410
|
];
|
|
386
411
|
landing = rows.length - 1;
|
|
387
412
|
return { ...list, items: [...list.items.slice(0, index), ...rows, ...list.items.slice(index + 1)] };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function looksLikeMarkdown(input: string): boolean;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const LIMIT = 200_000;
|
|
2
|
+
const LINE_LIMIT = 2_000;
|
|
3
|
+
const isList = (line) => /^ {0,6}(?:[-*+]|\d{1,9}[.)])[ \t]+\S/.test(line.slice(0, 64));
|
|
4
|
+
const isRow = (line) => line.includes("|") && /^[\s|:-]+$/.test(line) && line.includes("-");
|
|
5
|
+
function pair(text, mark) {
|
|
6
|
+
let from = 0;
|
|
7
|
+
for (let tries = 0; tries < 50; tries++) {
|
|
8
|
+
const a = text.indexOf(mark, from);
|
|
9
|
+
if (a === -1)
|
|
10
|
+
return false;
|
|
11
|
+
const inner = a + mark.length;
|
|
12
|
+
if (!/\s/.test(text.charAt(inner)) && inner < text.length) {
|
|
13
|
+
const b = text.indexOf(mark, inner + 1);
|
|
14
|
+
if (b !== -1 && !/\s/.test(text.charAt(b - 1)) && text.slice(inner, b).indexOf("\n\n") === -1)
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
from = a + mark.length;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function link(text) {
|
|
22
|
+
let from = 0;
|
|
23
|
+
for (let tries = 0; tries < 50; tries++) {
|
|
24
|
+
const mid = text.indexOf("](", from);
|
|
25
|
+
if (mid === -1)
|
|
26
|
+
return false;
|
|
27
|
+
const open = text.lastIndexOf("[", mid);
|
|
28
|
+
const close = text.indexOf(")", mid);
|
|
29
|
+
const label = open === -1 ? "" : text.slice(open + 1, mid);
|
|
30
|
+
if (open !== -1 && close !== -1 && label !== "" && !label.includes("\n") && close - mid < 2000)
|
|
31
|
+
return true;
|
|
32
|
+
from = mid + 2;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
export function looksLikeMarkdown(input) {
|
|
37
|
+
const text = input.length > LIMIT ? input.slice(0, LIMIT) : input;
|
|
38
|
+
const lines = text.split(/\r\n?|\n/);
|
|
39
|
+
let headings = 0, listLines = 0, tasks = 0, quotes = 0, notes = 0;
|
|
40
|
+
let fence = false, table = false;
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
const line = lines[i].slice(0, LINE_LIMIT);
|
|
43
|
+
if (/^ {0,3}(?:`{3,}|~{3,})/.test(line))
|
|
44
|
+
fence = true;
|
|
45
|
+
else if (/^ {0,3}#{1,6}[ \t]+\S/.test(line))
|
|
46
|
+
headings++;
|
|
47
|
+
else if (/^ {0,3}>/.test(line))
|
|
48
|
+
quotes++;
|
|
49
|
+
else if (/^ {0,3}\[\^[^\]\s]{1,64}\]:[ \t]*\S/.test(line))
|
|
50
|
+
notes++;
|
|
51
|
+
else if (isList(line)) {
|
|
52
|
+
listLines++;
|
|
53
|
+
if (/^\s*(?:[-*+]|\d+[.)])[ \t]+\[[ xX]\][ \t]/.test(line))
|
|
54
|
+
tasks++;
|
|
55
|
+
}
|
|
56
|
+
else if (line.includes("|") && i + 1 < lines.length && isRow(lines[i + 1].slice(0, LINE_LIMIT)) && line.replace(/[\s|]/g, "") !== "")
|
|
57
|
+
table = true;
|
|
58
|
+
}
|
|
59
|
+
let score = 0;
|
|
60
|
+
if (fence)
|
|
61
|
+
score += 3;
|
|
62
|
+
if (table)
|
|
63
|
+
score += 3;
|
|
64
|
+
if (headings > 0)
|
|
65
|
+
score += headings > 1 ? 2 : 1;
|
|
66
|
+
if (listLines >= 2)
|
|
67
|
+
score += 2;
|
|
68
|
+
if (tasks > 0)
|
|
69
|
+
score += 2;
|
|
70
|
+
if (quotes > 0)
|
|
71
|
+
score += 1;
|
|
72
|
+
if (notes > 0)
|
|
73
|
+
score += 2;
|
|
74
|
+
if (link(text))
|
|
75
|
+
score += 2;
|
|
76
|
+
if (pair(text, "**") || pair(text, "~~"))
|
|
77
|
+
score += 2;
|
|
78
|
+
if (pair(text, "`"))
|
|
79
|
+
score += 1;
|
|
80
|
+
return score >= 2;
|
|
81
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Doc } from "../model.ts";
|
|
2
|
+
/** The document as Markdown (CommonMark with the GitHub table, task item and strikethrough additions). */
|
|
3
|
+
export declare function exportMarkdown(doc: Doc, options?: MarkdownExportOptions): string;
|
|
4
|
+
export interface MarkdownExportOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Write underline, superscript and subscript as `<u>`, `<sup>` and `<sub>`, and a line break in a table cell or heading as `<br>`. The
|
|
7
|
+
* reader here understands those tags, so they come back; other Markdown readers show them as HTML. Default false: those details are dropped.
|
|
8
|
+
*/
|
|
9
|
+
readonly inlineHtml?: boolean;
|
|
10
|
+
}
|