@rathkit/rte-core 0.1.0-alpha.0 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom/render.js +25 -14
- package/dist/dom/view.d.ts +5 -3
- package/dist/dom/view.js +41 -23
- package/dist/edit.js +6 -3
- package/dist/engine.d.ts +7 -1
- package/dist/engine.js +19 -3
- 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/images/images.js +4 -3
- package/dist/images/index.d.ts +5 -1
- package/dist/images/index.js +3 -1
- package/dist/{dom/imageview.d.ts → images/view.d.ts} +8 -5
- package/dist/{dom/imageview.js → images/view.js} +16 -10
- package/dist/import.js +55 -18
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lists/lists.js +45 -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 +35 -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 +53 -0
- package/dist/model.d.ts +25 -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/path.d.ts +3 -1
- package/dist/path.js +91 -5
- package/dist/plugin.d.ts +52 -1
- package/dist/standard/index.d.ts +5 -2
- package/dist/standard/index.js +6 -4
- package/dist/{dom/tableview.d.ts → tables/cells.d.ts} +6 -5
- package/dist/{dom/tableview.js → tables/cells.js} +13 -9
- package/dist/{dom/tablegrips.d.ts → tables/grips.d.ts} +5 -4
- package/dist/{dom/tablegrips.js → tables/grips.js} +8 -7
- 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/package.json +23 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const MAX_KEPT = 64;
|
|
2
|
+
export function createReconciler(ops) {
|
|
3
|
+
const kept = new Map();
|
|
4
|
+
const keep = (id, item) => {
|
|
5
|
+
kept.delete(id);
|
|
6
|
+
kept.set(id, item);
|
|
7
|
+
if (kept.size > MAX_KEPT)
|
|
8
|
+
kept.delete(kept.keys().next().value);
|
|
9
|
+
};
|
|
10
|
+
const paragraph = { type: "br", paragraph: true };
|
|
11
|
+
const join = (a, b) => {
|
|
12
|
+
if (a.children.length > 0 && b.content.length > 0) {
|
|
13
|
+
const text = { type: "p", content: b.content };
|
|
14
|
+
return { item: { ...a, children: [...a.children, text, ...b.children] }, add: 0, childAdd: a.children.length };
|
|
15
|
+
}
|
|
16
|
+
const both = a.content.length > 0;
|
|
17
|
+
return {
|
|
18
|
+
item: { ...a, content: ops.normalizeInline([...a.content, ...(both ? [paragraph] : []), ...b.content]), children: [...a.children, ...b.children] },
|
|
19
|
+
add: both ? ops.inlineLength(a.content) + 1 : ops.inlineLength(a.content), childAdd: a.children.length,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
return (doc, sel) => {
|
|
23
|
+
let current = doc;
|
|
24
|
+
const found = ops.noteIds(current);
|
|
25
|
+
const lists = current.blocks.flatMap((block, b) => (ops.isNotes(block) ? [b] : []));
|
|
26
|
+
if (found.length === 0 && lists.length === 0)
|
|
27
|
+
return { doc, selection: sel };
|
|
28
|
+
const copies = new Map();
|
|
29
|
+
if (new Set(found).size !== found.length) {
|
|
30
|
+
const used = new Set(found);
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const fresh = () => { for (let n = used.size + 1;; n++)
|
|
33
|
+
if (!used.has(`n${n}`)) {
|
|
34
|
+
used.add(`n${n}`);
|
|
35
|
+
return `n${n}`;
|
|
36
|
+
} };
|
|
37
|
+
for (const path of ops.containerPaths(current)) {
|
|
38
|
+
if (lists.includes(path[0]))
|
|
39
|
+
continue;
|
|
40
|
+
const content = ops.getContent(current, path);
|
|
41
|
+
if (!content.some((n) => n.type === "note" && seen.has(n.id))) {
|
|
42
|
+
for (const n of content)
|
|
43
|
+
if (n.type === "note")
|
|
44
|
+
seen.add(n.id);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
current = ops.setContent(current, path, content.map((n) => {
|
|
48
|
+
if (n.type !== "note")
|
|
49
|
+
return n;
|
|
50
|
+
if (!seen.has(n.id)) {
|
|
51
|
+
seen.add(n.id);
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
const id = fresh();
|
|
55
|
+
copies.set(id, n.id);
|
|
56
|
+
return { type: "note", id };
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const ids = ops.noteIds(current);
|
|
61
|
+
const groups = new Map();
|
|
62
|
+
const moved = new Map();
|
|
63
|
+
let lead;
|
|
64
|
+
const leading = [];
|
|
65
|
+
let last;
|
|
66
|
+
let flat = 0;
|
|
67
|
+
const order = ids.filter((id, i) => ids.indexOf(id) === i);
|
|
68
|
+
for (const b of lists) {
|
|
69
|
+
current.blocks[b].items.forEach((item) => {
|
|
70
|
+
const id = item.id;
|
|
71
|
+
if (id !== undefined && !groups.has(id)) {
|
|
72
|
+
const joined = lead ? join(lead, item) : { item, add: 0, childAdd: 0 };
|
|
73
|
+
groups.set(id, joined.item === item ? item : { ...joined.item, id });
|
|
74
|
+
last = id;
|
|
75
|
+
for (const n of leading)
|
|
76
|
+
moved.set(n, { index: order.indexOf(id), add: moved.get(n)?.add ?? 0, childAdd: 0 });
|
|
77
|
+
moved.set(flat, { index: order.indexOf(id), add: joined.add, childAdd: joined.childAdd });
|
|
78
|
+
lead = undefined;
|
|
79
|
+
leading.length = 0;
|
|
80
|
+
}
|
|
81
|
+
else if (last !== undefined) {
|
|
82
|
+
const { item: next, add, childAdd } = join(groups.get(last), item);
|
|
83
|
+
groups.set(last, next);
|
|
84
|
+
moved.set(flat, { index: order.indexOf(last), add, childAdd });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const joined = lead ? join(lead, item) : { item, add: 0, childAdd: 0 };
|
|
88
|
+
lead = joined.item;
|
|
89
|
+
moved.set(flat, { index: -1, add: joined.add, childAdd: joined.childAdd });
|
|
90
|
+
leading.push(flat);
|
|
91
|
+
}
|
|
92
|
+
flat++;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (lead && order[0] !== undefined) {
|
|
96
|
+
const target = order[0];
|
|
97
|
+
groups.set(target, groups.has(target) ? join(lead, groups.get(target)).item : { ...lead, id: target });
|
|
98
|
+
for (const n of leading)
|
|
99
|
+
moved.set(n, { index: 0, add: moved.get(n)?.add ?? 0, childAdd: 0 });
|
|
100
|
+
}
|
|
101
|
+
for (const [id, item] of groups)
|
|
102
|
+
if (!order.includes(id))
|
|
103
|
+
keep(id, item);
|
|
104
|
+
const wanted = order.map((id) => {
|
|
105
|
+
const have = groups.get(id);
|
|
106
|
+
if (have)
|
|
107
|
+
return have.id === id ? have : { ...have, id };
|
|
108
|
+
const source = copies.get(id);
|
|
109
|
+
const old = kept.get(id) ?? (source !== undefined ? groups.get(source) : undefined);
|
|
110
|
+
kept.delete(id);
|
|
111
|
+
return { ...(old ?? { content: [], children: [] }), id };
|
|
112
|
+
});
|
|
113
|
+
const first = lists[0];
|
|
114
|
+
const only = first !== undefined ? current.blocks[first] : undefined;
|
|
115
|
+
if (lists.length === 1 && only && only.items.length === wanted.length && only.items.every((item, i) => item === wanted[i]) && current === doc)
|
|
116
|
+
return { doc, selection: sel };
|
|
117
|
+
const block = wanted.length > 0 ? { ...(only ?? { type: "ol" }), type: "ol", notes: true, items: wanted } : undefined;
|
|
118
|
+
const blocks = [];
|
|
119
|
+
const newIndex = new Map();
|
|
120
|
+
current.blocks.forEach((b, i) => {
|
|
121
|
+
if (i === first) {
|
|
122
|
+
if (block) {
|
|
123
|
+
newIndex.set(i, blocks.length);
|
|
124
|
+
blocks.push(block);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (!lists.includes(i)) {
|
|
128
|
+
newIndex.set(i, blocks.length);
|
|
129
|
+
blocks.push(b);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const at = first === undefined && block ? blocks.length : (newIndex.get(first) ?? 0);
|
|
133
|
+
if (first === undefined && block)
|
|
134
|
+
blocks.push(block);
|
|
135
|
+
let offsetOf = 0;
|
|
136
|
+
const flatStart = new Map();
|
|
137
|
+
for (const b of lists) {
|
|
138
|
+
flatStart.set(b, offsetOf);
|
|
139
|
+
offsetOf += current.blocks[b].items.length;
|
|
140
|
+
}
|
|
141
|
+
const next = { blocks };
|
|
142
|
+
const map = (pos) => {
|
|
143
|
+
const [b, i, ...rest] = pos.path;
|
|
144
|
+
if (lists.includes(b)) {
|
|
145
|
+
const to = moved.get(flatStart.get(b) + i);
|
|
146
|
+
if (!to || !block)
|
|
147
|
+
return ops.clampPos(next, { path: [Math.min(at, blocks.length - 1)], offset: 0 });
|
|
148
|
+
const [k, ...deeper] = rest;
|
|
149
|
+
return { path: k === undefined ? [at, to.index] : [at, to.index, k + to.childAdd, ...deeper], offset: pos.offset + (rest.length === 0 ? to.add : 0) };
|
|
150
|
+
}
|
|
151
|
+
return ops.clampPos(next, { path: [newIndex.get(b) ?? b, ...pos.path.slice(1)], offset: pos.offset });
|
|
152
|
+
};
|
|
153
|
+
return { doc: next, selection: { anchor: map(sel.anchor), focus: map(sel.focus) } };
|
|
154
|
+
};
|
|
155
|
+
}
|
package/dist/images/images.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decodeBase64 } from "../base64.js";
|
|
2
|
+
import { asChildren } from "../path.js";
|
|
2
3
|
export function dataImageBytes(src) {
|
|
3
4
|
const comma = src.indexOf(",");
|
|
4
5
|
if (!/^data:/i.test(src) || comma === -1 || !/;base64$/i.test(src.slice(0, comma)))
|
|
@@ -26,8 +27,8 @@ export function mapImages(blocks, fn) {
|
|
|
26
27
|
const list = (l) => {
|
|
27
28
|
const items = l.items.flatMap((item) => {
|
|
28
29
|
const { content, emptied } = inline(item.content);
|
|
29
|
-
const
|
|
30
|
-
return emptied &&
|
|
30
|
+
const children = asChildren(walk(item.children));
|
|
31
|
+
return emptied && children.length === 0 ? [] : [{ ...item, content, children }];
|
|
31
32
|
});
|
|
32
33
|
return items.length > 0 || l.items.length === 0 ? { ...l, items } : undefined;
|
|
33
34
|
};
|
|
@@ -74,7 +75,7 @@ export function collectImages(blocks) {
|
|
|
74
75
|
const inline = (content) => { for (const node of content)
|
|
75
76
|
if (node.type === "image")
|
|
76
77
|
found.push(node); };
|
|
77
|
-
const list = (l) => l.items.forEach((item) => { inline(item.content); item.
|
|
78
|
+
const list = (l) => l.items.forEach((item) => { inline(item.content); walk(item.children); });
|
|
78
79
|
const walk = (bs) => {
|
|
79
80
|
for (const block of bs) {
|
|
80
81
|
if (block.type === "p" || block.type === "h")
|
package/dist/images/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ImagesOptions } from "./plugin.ts";
|
|
2
|
+
import type { Plugin } from "../plugin.ts";
|
|
3
|
+
export type { ImagesOptions };
|
|
4
|
+
/** The images plugin, with the part that selects and resizes images in a browser. (`./plugin.ts` is the same without it.) */
|
|
5
|
+
export declare const images: (options?: ImagesOptions) => Plugin;
|
|
2
6
|
export { collectImages, dataImageBytes, isDataImage, mapImages, readDataImage, rewriteImages } from "./images.ts";
|
package/dist/images/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { images as engineOnly } from "./plugin.js";
|
|
2
|
+
import { imagesView } from "./view.js";
|
|
3
|
+
export const images = (options) => ({ ...engineOnly(options), view: imagesView });
|
|
2
4
|
export { collectImages, dataImageBytes, isDataImage, mapImages, readDataImage, rewriteImages } from "./images.js";
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ViewExtension, ViewHost } from "../plugin.ts";
|
|
2
2
|
/** The attribute on the selected image, for styling. */
|
|
3
3
|
export declare const SELECTED_IMAGE = "data-rk-image-selected";
|
|
4
4
|
export declare class ImageSelection {
|
|
5
|
-
private readonly
|
|
6
|
-
private readonly engine;
|
|
7
|
-
private readonly quiet;
|
|
5
|
+
private readonly host;
|
|
8
6
|
private marked;
|
|
9
7
|
private handle;
|
|
10
8
|
private drag;
|
|
11
9
|
private readonly stops;
|
|
12
|
-
|
|
10
|
+
private readonly root;
|
|
11
|
+
private readonly engine;
|
|
12
|
+
private readonly quiet;
|
|
13
|
+
constructor(host: ViewHost);
|
|
13
14
|
/** The drawn element of the selected image, or undefined. */
|
|
14
15
|
private find;
|
|
15
16
|
update(): void;
|
|
@@ -19,3 +20,5 @@ export declare class ImageSelection {
|
|
|
19
20
|
private start;
|
|
20
21
|
destroy(): void;
|
|
21
22
|
}
|
|
23
|
+
/** Images in the page: a click selects one, and its corner can be dragged to resize it. Comes with the images plugin. */
|
|
24
|
+
export declare const imagesView: ViewExtension;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { imageSelection, selectedImage } from "../imageselect.js";
|
|
2
|
-
import { domFromPos, posFromDom } from "./render.js";
|
|
3
2
|
export const SELECTED_IMAGE = "data-rk-image-selected";
|
|
4
3
|
const MIN = 16;
|
|
5
4
|
export class ImageSelection {
|
|
6
|
-
|
|
7
|
-
engine;
|
|
8
|
-
quiet;
|
|
5
|
+
host;
|
|
9
6
|
marked;
|
|
10
7
|
handle;
|
|
11
8
|
drag;
|
|
12
9
|
stops = [];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
root;
|
|
11
|
+
engine;
|
|
12
|
+
quiet;
|
|
13
|
+
constructor(host) {
|
|
14
|
+
this.host = host;
|
|
15
|
+
this.root = host.element;
|
|
16
|
+
this.engine = host.engine;
|
|
17
|
+
this.quiet = () => host.quiet();
|
|
17
18
|
const reposition = () => this.place();
|
|
18
19
|
window.addEventListener("scroll", reposition, true);
|
|
19
20
|
window.addEventListener("resize", reposition);
|
|
@@ -22,7 +23,7 @@ export class ImageSelection {
|
|
|
22
23
|
const target = e.target;
|
|
23
24
|
if (!(target instanceof HTMLImageElement) || !this.root.contains(target) || this.engine.readOnly)
|
|
24
25
|
return;
|
|
25
|
-
const at = posFromDom(
|
|
26
|
+
const at = this.host.posFromDom(target.parentNode, [...target.parentNode.childNodes].indexOf(target));
|
|
26
27
|
if (at)
|
|
27
28
|
this.engine.setSelection(imageSelection(at.path, at.offset));
|
|
28
29
|
};
|
|
@@ -34,7 +35,7 @@ export class ImageSelection {
|
|
|
34
35
|
const picked = selectedImage(this.engine.document, this.engine.selection);
|
|
35
36
|
if (!picked)
|
|
36
37
|
return undefined;
|
|
37
|
-
const at = domFromPos(
|
|
38
|
+
const at = this.host.domFromPos({ path: picked.path, offset: picked.offset + 1 });
|
|
38
39
|
const el = !at ? undefined : at.node.nodeType === Node.TEXT_NODE ? at.node.previousSibling : at.node.childNodes[at.offset - 1];
|
|
39
40
|
return el instanceof HTMLImageElement ? el : undefined;
|
|
40
41
|
}
|
|
@@ -120,3 +121,8 @@ export class ImageSelection {
|
|
|
120
121
|
this.marked?.removeAttribute(SELECTED_IMAGE);
|
|
121
122
|
}
|
|
122
123
|
}
|
|
124
|
+
export const imagesView = (host) => {
|
|
125
|
+
const selection = new ImageSelection(host);
|
|
126
|
+
const stop = host.onChange(() => selection.update());
|
|
127
|
+
return () => { stop(); selection.destroy(); };
|
|
128
|
+
};
|
package/dist/import.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { decodeEntities } from "./html/entities.js";
|
|
2
2
|
import { parseHtml } from "./html/parse.js";
|
|
3
3
|
import { parseLine, SIDES, withSides } from "./cellborder.js";
|
|
4
|
-
import { COLOR_VALUE, COLUMN_WIDTH, MAX_INDENT, ROW_HEIGHT, inlineText, normalizeInline, } from "./model.js";
|
|
4
|
+
import { COLOR_VALUE, COLUMN_WIDTH, MAX_INDENT, NOTE_ID, ROW_HEIGHT, inlineText, normalizeInline, } from "./model.js";
|
|
5
|
+
import { asChildren } from "./path.js";
|
|
6
|
+
import { DEFAULT_MARKER } from "./model.js";
|
|
5
7
|
const DROP = new Set([
|
|
6
8
|
"script", "style", "link", "meta", "base", "object", "embed", "iframe", "frame", "frameset", "noscript", "template",
|
|
7
9
|
"head", "textarea", "input", "select", "option", "button", "canvas", "audio", "video", "applet", "xml",
|
|
@@ -275,6 +277,12 @@ function inlineOf(nodes, ctx, settings) {
|
|
|
275
277
|
content.push(...(content.length > 0 ? [{ type: "br" }] : []), ...block.content);
|
|
276
278
|
return normalizeInline(content);
|
|
277
279
|
}
|
|
280
|
+
const NOTE_PREFIX = /^(?:user-content-)?(?:fnref|fn)[-:_]?/i;
|
|
281
|
+
const noteId = (raw, strip) => {
|
|
282
|
+
const id = strip ? raw?.replace(/^#/, "").replace(NOTE_PREFIX, "").replace(/[^\w-]/g, "") : raw;
|
|
283
|
+
return id !== undefined && NOTE_ID.test(id) ? id : undefined;
|
|
284
|
+
};
|
|
285
|
+
const hasClass = (el, name) => (el.attrs.class ?? "").split(/\s+/).includes(name);
|
|
278
286
|
function convertElement(el, sink, ctx, settings) {
|
|
279
287
|
const tag = el.tag;
|
|
280
288
|
if (DROP.has(tag) || tag === "title")
|
|
@@ -291,7 +299,7 @@ function convertElement(el, sink, ctx, settings) {
|
|
|
291
299
|
type: listItem.type,
|
|
292
300
|
...(listItem.start !== undefined && listItem.start !== 1 ? { start: listItem.start } : {}),
|
|
293
301
|
...(listItem.style !== undefined ? { style: listItem.style } : {}),
|
|
294
|
-
items: [{ content: normalizeInline(listItem.content),
|
|
302
|
+
items: [{ content: normalizeInline(listItem.content), children: [], level: listItem.level, listId: listItem.listId }],
|
|
295
303
|
});
|
|
296
304
|
}
|
|
297
305
|
return;
|
|
@@ -304,6 +312,22 @@ function convertElement(el, sink, ctx, settings) {
|
|
|
304
312
|
sink.flush((content) => ({ type: "h", level, content: withoutBold(content) }), true);
|
|
305
313
|
return;
|
|
306
314
|
}
|
|
315
|
+
if (tag === "a" && (el.attrs.role === "doc-backlink" || el.attrs["data-footnote-backref"] !== undefined || hasClass(el, "footnote-back") || hasClass(el, "footnote-backref")))
|
|
316
|
+
return;
|
|
317
|
+
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")))) {
|
|
318
|
+
const id = tag === "sup" ? noteId(el.attrs["data-note"], false) : noteId(el.attrs.href, true);
|
|
319
|
+
if (id !== undefined) {
|
|
320
|
+
sink.push({ type: "note", id });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (el.attrs.role === "doc-endnotes" || el.attrs["data-footnotes"] !== undefined || hasClass(el, "footnotes")) {
|
|
325
|
+
sink.flush();
|
|
326
|
+
for (const ol of el.tag === "ol" ? [el] : el.children)
|
|
327
|
+
if (ol.type === "el" && ol.tag === "ol")
|
|
328
|
+
sink.blocks.push(convertList(ol, ctx, settings, true));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
307
331
|
switch (tag) {
|
|
308
332
|
case "br":
|
|
309
333
|
sink.push({ type: "br" });
|
|
@@ -452,18 +476,17 @@ function listStyleOf(el) {
|
|
|
452
476
|
return style && /^[a-z-]+$/.test(style) && !/^(initial|inherit|unset|revert|none)$/.test(style) ? style : undefined;
|
|
453
477
|
}
|
|
454
478
|
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
479
|
function taskOf(li) {
|
|
457
480
|
const own = (li.attrs["aria-checked"] ?? li.attrs["data-checked"] ?? "").toLowerCase();
|
|
458
481
|
const classes = (li.attrs.class ?? "").toLowerCase().split(/\s+/);
|
|
459
482
|
const fromMarkup = own === "true" || classes.includes("checked") || classes.includes("task-done") ? true : own === "false" || classes.includes("unchecked") ? false : undefined;
|
|
460
483
|
const kids = li.children;
|
|
461
|
-
const at = kids.findIndex((n) => !
|
|
484
|
+
const at = kids.findIndex((n) => !isBlank(n));
|
|
462
485
|
const first = at === -1 ? undefined : kids[at];
|
|
463
486
|
if (first && isCheckbox(first))
|
|
464
487
|
return { checked: "checked" in first.attrs, children: [...kids.slice(0, at), ...kids.slice(at + 1)] };
|
|
465
488
|
if (first && first.type === "el" && /^(label|p|span|div)$/.test(first.tag)) {
|
|
466
|
-
const inner = first.children.findIndex((n) => !
|
|
489
|
+
const inner = first.children.findIndex((n) => !isBlank(n));
|
|
467
490
|
const box = inner === -1 ? undefined : first.children[inner];
|
|
468
491
|
if (box && isCheckbox(box)) {
|
|
469
492
|
const wrapper = { ...first, children: [...first.children.slice(0, inner), ...first.children.slice(inner + 1)] };
|
|
@@ -472,8 +495,10 @@ function taskOf(li) {
|
|
|
472
495
|
}
|
|
473
496
|
return fromMarkup === undefined ? undefined : { checked: fromMarkup, children: kids };
|
|
474
497
|
}
|
|
475
|
-
function convertList(el, ctx, settings) {
|
|
498
|
+
function convertList(el, ctx, settings, notes = false) {
|
|
476
499
|
const list = { type: el.tag === "ol" ? "ol" : "ul", items: [] };
|
|
500
|
+
if (notes)
|
|
501
|
+
list.notes = true;
|
|
477
502
|
const start = el.tag === "ol" && el.attrs.start !== undefined ? Number.parseInt(el.attrs.start, 10) : NaN;
|
|
478
503
|
if (Number.isFinite(start) && start !== 1 && start >= 0 && start < 100000)
|
|
479
504
|
list.start = start;
|
|
@@ -483,8 +508,8 @@ function convertList(el, ctx, settings) {
|
|
|
483
508
|
continue;
|
|
484
509
|
if (child.tag === "ul" || child.tag === "ol") {
|
|
485
510
|
const nested = convertList(child, ctx, settings);
|
|
486
|
-
const last = list.items[list.items.length - 1] ?? list.items[list.items.push({ content: [],
|
|
487
|
-
last.
|
|
511
|
+
const last = list.items[list.items.length - 1] ?? list.items[list.items.push({ content: [], children: [] }) - 1];
|
|
512
|
+
last.children.push(nested);
|
|
488
513
|
continue;
|
|
489
514
|
}
|
|
490
515
|
if (child.tag !== "li")
|
|
@@ -497,19 +522,32 @@ function convertList(el, ctx, settings) {
|
|
|
497
522
|
const task = taskOf(child);
|
|
498
523
|
convertNodes(task ? task.children : child.children, inner, ctx, settings);
|
|
499
524
|
inner.flush();
|
|
500
|
-
const item = { content: [],
|
|
525
|
+
const item = { content: [], children: [] };
|
|
501
526
|
if (task)
|
|
502
527
|
item.checked = task.checked;
|
|
528
|
+
let leading = true;
|
|
503
529
|
for (const block of inner.blocks) {
|
|
504
|
-
if (block.type === "
|
|
505
|
-
item.sublists.push(block);
|
|
506
|
-
else if (block.type === "p" || block.type === "h") {
|
|
530
|
+
if (leading && (block.type === "p" || block.type === "h")) {
|
|
507
531
|
if (item.content.length > 0)
|
|
508
532
|
item.content.push({ type: "br", paragraph: true });
|
|
509
533
|
item.content.push(...block.content);
|
|
534
|
+
continue;
|
|
510
535
|
}
|
|
536
|
+
leading = false;
|
|
537
|
+
if (isList(block))
|
|
538
|
+
item.children.push(block);
|
|
539
|
+
else
|
|
540
|
+
item.children.push(...asChildren([block]));
|
|
511
541
|
}
|
|
512
542
|
item.content = normalizeInline(item.content);
|
|
543
|
+
if (notes) {
|
|
544
|
+
const tail = item.content[item.content.length - 1];
|
|
545
|
+
if (tail?.type === "text")
|
|
546
|
+
item.content = normalizeInline([...item.content.slice(0, -1), { ...tail, text: tail.text.trimEnd() }]);
|
|
547
|
+
const id = noteId(child.attrs["data-note"], false) ?? noteId(child.attrs.id, true);
|
|
548
|
+
if (id !== undefined)
|
|
549
|
+
item.id = id;
|
|
550
|
+
}
|
|
513
551
|
const level = levelOf(child.attrs["data-aria-level"] ?? child.attrs["aria-level"]);
|
|
514
552
|
if (level !== undefined)
|
|
515
553
|
item.level = level;
|
|
@@ -699,7 +737,6 @@ function padRows(rows) {
|
|
|
699
737
|
return missing > 0 ? [...cells, ...Array.from({ length: missing }, () => ({ header: false, colspan: 1, rowspan: 1, blocks: [{ type: "p", content: [] }] }))] : cells;
|
|
700
738
|
});
|
|
701
739
|
}
|
|
702
|
-
const DEFAULT_MARKER = (type, depth) => (type === "ol" ? "decimal" : ["disc", "circle", "square"][Math.min(depth, 2)]);
|
|
703
740
|
const isList = (block) => block.type === "ul" || block.type === "ol";
|
|
704
741
|
const hinted = (list) => list.items.length > 0 && list.items.every((item) => item.level !== undefined);
|
|
705
742
|
function finishBlocks(blocks) {
|
|
@@ -744,8 +781,8 @@ function nestByLevel(lists) {
|
|
|
744
781
|
const fresh = { type: list.type, items: [] };
|
|
745
782
|
const parent = stack[stack.length - 1];
|
|
746
783
|
if (parent) {
|
|
747
|
-
const host = parent.items[parent.items.length - 1] ?? parent.items[parent.items.push({ content: [],
|
|
748
|
-
host.
|
|
784
|
+
const host = parent.items[parent.items.length - 1] ?? parent.items[parent.items.push({ content: [], children: [] }) - 1];
|
|
785
|
+
host.children.push(fresh);
|
|
749
786
|
}
|
|
750
787
|
else {
|
|
751
788
|
result.push(fresh);
|
|
@@ -757,15 +794,15 @@ function nestByLevel(lists) {
|
|
|
757
794
|
stack.push(fresh);
|
|
758
795
|
ids.push(item.listId);
|
|
759
796
|
}
|
|
760
|
-
stack[stack.length - 1].items.push({ content: item.content,
|
|
797
|
+
stack[stack.length - 1].items.push({ content: item.content, children: item.children, ...(item.checked !== undefined ? { checked: item.checked } : {}) });
|
|
761
798
|
}
|
|
762
799
|
}
|
|
763
800
|
return result.map((l) => freezeList(l, 0));
|
|
764
801
|
}
|
|
765
802
|
function freezeList(list, depth) {
|
|
766
|
-
const items = list.items.map((item) => ({ content: item.content,
|
|
803
|
+
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 } : {}) }));
|
|
767
804
|
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 };
|
|
805
|
+
return { type: list.type, ...(list.notes ? { notes: true } : {}), ...(list.start !== undefined ? { start: list.start } : {}), ...(style !== undefined ? { style } : {}), items };
|
|
769
806
|
}
|
|
770
807
|
function repairRewrittenSpaces(nodes) {
|
|
771
808
|
return nodes.map((node) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { checkImageSrc, importBlocks, importHtml, importText, safeUrl, parseStyl
|
|
|
4
4
|
export { exportBlock, exportHtml, exportInline } from "./export.ts";
|
|
5
5
|
export * from "./path.ts";
|
|
6
6
|
export * from "./edit.ts";
|
|
7
|
+
export type { CopyFormatter, Normalizer } from "./plugin.ts";
|
|
8
|
+
export { isNotes, noteIds } from "./notes.ts";
|
|
7
9
|
export { defaultPaste, extractFragment, pasteBlocks, type ClipboardData, type PasteHandler } from "./paste.ts";
|
|
8
10
|
export { decodeBase64, encodeBase64 } from "./base64.ts";
|
|
9
11
|
export { createEngine } from "./core.ts";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { checkImageSrc, importBlocks, importHtml, importText, safeUrl, parseStyl
|
|
|
4
4
|
export { exportBlock, exportHtml, exportInline } from "./export.js";
|
|
5
5
|
export * from "./path.js";
|
|
6
6
|
export * from "./edit.js";
|
|
7
|
+
export { isNotes, noteIds } from "./notes.js";
|
|
7
8
|
export { defaultPaste, extractFragment, pasteBlocks } from "./paste.js";
|
|
8
9
|
export { decodeBase64, encodeBase64 } from "./base64.js";
|
|
9
10
|
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 }] : [];
|
|
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 }] : []), ...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: [] }), ...rest] };
|
|
260
275
|
});
|
|
261
276
|
}
|
|
262
277
|
return { doc: next, selection: carry(doc, next, sel) };
|
|
@@ -279,16 +294,16 @@ export function outdentItems(doc, sel) {
|
|
|
279
294
|
const itemIndex = path.at(-1);
|
|
280
295
|
next = mapListAt(next, parentPath, (list, parentIndex) => {
|
|
281
296
|
const parent = list.items[parentIndex];
|
|
282
|
-
const sub = parent.
|
|
297
|
+
const sub = parent.children[sublistIndex];
|
|
283
298
|
const moved = sub.items[itemIndex];
|
|
284
299
|
const followers = sub.items.slice(itemIndex + 1);
|
|
285
300
|
const keptSub = itemIndex > 0 ? [{ ...sub, items: sub.items.slice(0, itemIndex) }] : [];
|
|
286
301
|
const lifted = {
|
|
287
302
|
content: moved.content,
|
|
288
|
-
|
|
303
|
+
children: [...moved.children, ...(followers.length > 0 ? [{ type: sub.type, ...(sub.style !== undefined ? { style: sub.style } : {}), items: followers }] : [])],
|
|
289
304
|
};
|
|
290
|
-
const
|
|
291
|
-
const items = [...list.items.slice(0, parentIndex), { content: parent.content,
|
|
305
|
+
const children = [...parent.children.slice(0, sublistIndex), ...keptSub, ...parent.children.slice(sublistIndex + 1)];
|
|
306
|
+
const items = [...list.items.slice(0, parentIndex), { content: parent.content, children }, lifted, ...list.items.slice(parentIndex + 1)];
|
|
292
307
|
return { ...list, items };
|
|
293
308
|
});
|
|
294
309
|
}
|
|
@@ -351,7 +366,7 @@ export function splitListItem(doc, pos) {
|
|
|
351
366
|
const item = held?.list.items[held.index];
|
|
352
367
|
if (!loc || !item)
|
|
353
368
|
return undefined;
|
|
354
|
-
if (content.length === 0 && item.
|
|
369
|
+
if (content.length === 0 && item.children.length === 0)
|
|
355
370
|
return leaveListItem(doc, pos);
|
|
356
371
|
const before = sliceInline(content, 0, pos.offset);
|
|
357
372
|
const after = sliceInline(content, pos.offset);
|
|
@@ -359,8 +374,8 @@ export function splitListItem(doc, pos) {
|
|
|
359
374
|
const next = mapListAt(doc, pos.path, (list, index) => ({
|
|
360
375
|
...list,
|
|
361
376
|
items: list.items.flatMap((candidate, i) => (i !== index ? [candidate] : [
|
|
362
|
-
{ content: before,
|
|
363
|
-
{ content: after,
|
|
377
|
+
{ content: before, children: [], ...(item.checked !== undefined ? { checked: item.checked } : {}), ...(item.id !== undefined ? { id: item.id } : {}) },
|
|
378
|
+
{ content: after, children: item.children, ...(item.checked !== undefined ? { checked: false } : {}) },
|
|
364
379
|
])),
|
|
365
380
|
}));
|
|
366
381
|
return { doc: next, selection: caret({ path: containerPaths(next)[ordinal + 1] ?? pos.path, offset: 0 }) };
|
|
@@ -377,11 +392,11 @@ export function insertIntoList(doc, pos, blocks, before, after) {
|
|
|
377
392
|
if (!item)
|
|
378
393
|
return list;
|
|
379
394
|
const rows = others.length === 0
|
|
380
|
-
? [{ content: normalizeInline([...before, ...first, ...after])
|
|
395
|
+
? [{ ...item, content: normalizeInline([...before, ...first, ...after]) }]
|
|
381
396
|
: [
|
|
382
|
-
{ content: normalizeInline([...before, ...first]),
|
|
383
|
-
...others.slice(0, -1).map((content) => ({ content,
|
|
384
|
-
{ content: normalizeInline([...lastText, ...after]),
|
|
397
|
+
{ content: normalizeInline([...before, ...first]), children: [], ...(item.checked !== undefined ? { checked: item.checked } : {}), ...(item.id !== undefined ? { id: item.id } : {}) },
|
|
398
|
+
...others.slice(0, -1).map((content) => ({ content, children: [], ...(item.checked !== undefined ? { checked: false } : {}) })),
|
|
399
|
+
{ content: normalizeInline([...lastText, ...after]), children: item.children, ...(item.checked !== undefined ? { checked: false } : {}) },
|
|
385
400
|
];
|
|
386
401
|
landing = rows.length - 1;
|
|
387
402
|
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;
|