@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.
Files changed (55) hide show
  1. package/dist/dom/render.js +25 -14
  2. package/dist/dom/view.d.ts +5 -3
  3. package/dist/dom/view.js +41 -23
  4. package/dist/edit.js +6 -3
  5. package/dist/engine.d.ts +7 -1
  6. package/dist/engine.js +19 -3
  7. package/dist/export.d.ts +2 -2
  8. package/dist/export.js +22 -6
  9. package/dist/footnotes/index.d.ts +2 -0
  10. package/dist/footnotes/index.js +2 -0
  11. package/dist/footnotes/plugin.d.ts +6 -0
  12. package/dist/footnotes/plugin.js +130 -0
  13. package/dist/footnotes/reconcile.d.ts +4 -0
  14. package/dist/footnotes/reconcile.js +155 -0
  15. package/dist/images/images.js +4 -3
  16. package/dist/images/index.d.ts +5 -1
  17. package/dist/images/index.js +3 -1
  18. package/dist/{dom/imageview.d.ts → images/view.d.ts} +8 -5
  19. package/dist/{dom/imageview.js → images/view.js} +16 -10
  20. package/dist/import.js +55 -18
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +1 -0
  23. package/dist/lists/lists.js +45 -30
  24. package/dist/markdown/detect.d.ts +1 -0
  25. package/dist/markdown/detect.js +81 -0
  26. package/dist/markdown/export.d.ts +10 -0
  27. package/dist/markdown/export.js +252 -0
  28. package/dist/markdown/index.d.ts +5 -0
  29. package/dist/markdown/index.js +4 -0
  30. package/dist/markdown/inline.d.ts +35 -0
  31. package/dist/markdown/inline.js +604 -0
  32. package/dist/markdown/parse.d.ts +4 -0
  33. package/dist/markdown/parse.js +586 -0
  34. package/dist/markdown/plugin.d.ts +20 -0
  35. package/dist/markdown/plugin.js +53 -0
  36. package/dist/model.d.ts +25 -2
  37. package/dist/model.js +2 -0
  38. package/dist/notes.d.ts +6 -0
  39. package/dist/notes.js +49 -0
  40. package/dist/ops.d.ts +6 -1
  41. package/dist/ops.js +5 -4
  42. package/dist/path.d.ts +3 -1
  43. package/dist/path.js +91 -5
  44. package/dist/plugin.d.ts +52 -1
  45. package/dist/standard/index.d.ts +5 -2
  46. package/dist/standard/index.js +6 -4
  47. package/dist/{dom/tableview.d.ts → tables/cells.d.ts} +6 -5
  48. package/dist/{dom/tableview.js → tables/cells.js} +13 -9
  49. package/dist/{dom/tablegrips.d.ts → tables/grips.d.ts} +5 -4
  50. package/dist/{dom/tablegrips.js → tables/grips.js} +8 -7
  51. package/dist/tables/index.d.ts +5 -1
  52. package/dist/tables/index.js +3 -1
  53. package/dist/tables/view.d.ts +2 -0
  54. package/dist/tables/view.js +15 -0
  55. 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
+ }
@@ -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 sublists = item.sublists.map(list).filter((x) => x !== undefined);
30
- return emptied && sublists.length === 0 ? [] : [{ content, sublists }];
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.sublists.forEach(list); });
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")
@@ -1,2 +1,6 @@
1
- export { images, type ImagesOptions } from "./plugin.ts";
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";
@@ -1,2 +1,4 @@
1
- export { images } from "./plugin.js";
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 { Engine } from "../engine.ts";
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 root;
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
- constructor(root: HTMLElement, engine: Engine, quiet: () => void);
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
- root;
7
- engine;
8
- quiet;
5
+ host;
9
6
  marked;
10
7
  handle;
11
8
  drag;
12
9
  stops = [];
13
- constructor(root, engine, quiet) {
14
- this.root = root;
15
- this.engine = engine;
16
- this.quiet = quiet;
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(this.root, target.parentNode, [...target.parentNode.childNodes].indexOf(target));
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(this.root, { path: picked.path, offset: picked.offset + 1 });
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), sublists: [], level: listItem.level, listId: listItem.listId }],
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) => !blankText(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) => !blankText(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: [], sublists: [] }) - 1];
487
- last.sublists.push(nested);
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: [], sublists: [] };
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 === "ul" || block.type === "ol")
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: [], sublists: [] }) - 1];
748
- host.sublists.push(fresh);
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, sublists: item.sublists, ...(item.checked !== undefined ? { checked: item.checked } : {}) });
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, sublists: item.sublists.map((sub) => freezeList(sub, depth + 1)), ...(item.checked !== undefined ? { checked: item.checked } : {}) }));
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";
@@ -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 { listBlock: [...prefix, n], item: more };
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]]?.sublists[steps[at + 1]];
34
- if (!sub)
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.sublists.length > 0 && !liftChildren ? [{ content: [], sublists: item.sublists }] : [];
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.sublists : [],
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.sublists[k], path.slice(2), liftChildren);
66
- const keptSublists = [...item.sublists.slice(0, k), ...(inner.before ? [inner.before] : [])];
67
- const afterSublists = [...(inner.after ? [inner.after] : []), ...item.sublists.slice(k + 1)];
68
- const rest = [...(afterSublists.length > 0 ? [{ content: [], sublists: afterSublists }] : []), ...list.items.slice(i + 1)];
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, sublists: keptSublists }] },
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, sublists: item.sublists.map((sub, s) => (s === k ? edit(sub, steps.slice(2)) : sub)) })) };
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, sublists: [] }));
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, sublists: item.sublists.flatMap((sub, s) => (s !== k ? [sub] : [edit(sub, steps.slice(2))].filter((x) => x !== undefined))) }),
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.sublists.at(-1);
251
- const sublists = last && last.type === list.type
252
- ? [...target.sublists.slice(0, -1), { ...last, items: [...last.items, ...flat] }]
253
- : [...target.sublists, { type: list.type, items: flat }];
254
- return { ...target, sublists };
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: [], sublists: [] }), ...rest] };
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.sublists[sublistIndex];
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
- sublists: [...moved.sublists, ...(followers.length > 0 ? [{ type: sub.type, ...(sub.style !== undefined ? { style: sub.style } : {}), items: followers }] : [])],
303
+ children: [...moved.children, ...(followers.length > 0 ? [{ type: sub.type, ...(sub.style !== undefined ? { style: sub.style } : {}), items: followers }] : [])],
289
304
  };
290
- const sublists = [...parent.sublists.slice(0, sublistIndex), ...keptSub, ...parent.sublists.slice(sublistIndex + 1)];
291
- const items = [...list.items.slice(0, parentIndex), { content: parent.content, sublists }, lifted, ...list.items.slice(parentIndex + 1)];
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.sublists.length === 0)
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, sublists: [], ...(item.checked !== undefined ? { checked: item.checked } : {}) },
363
- { content: after, sublists: item.sublists, ...(item.checked !== undefined ? { checked: false } : {}) },
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]), sublists: item.sublists }]
395
+ ? [{ ...item, content: normalizeInline([...before, ...first, ...after]) }]
381
396
  : [
382
- { content: normalizeInline([...before, ...first]), sublists: [], ...(item.checked !== undefined ? { checked: item.checked } : {}) },
383
- ...others.slice(0, -1).map((content) => ({ content, sublists: [], ...(item.checked !== undefined ? { checked: false } : {}) })),
384
- { content: normalizeInline([...lastText, ...after]), sublists: item.sublists, ...(item.checked !== undefined ? { checked: false } : {}) },
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;