@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
package/dist/model.d.ts CHANGED
@@ -31,7 +31,17 @@ export interface Image {
31
31
  readonly caption?: string;
32
32
  }
33
33
  export type ImageAlign = "left" | "right" | "center";
34
- export type Inline = TextRun | LineBreak | Image;
34
+ /**
35
+ * A footnote reference: one character in the text, shown as the note's number. The note itself is an item of the document's notes
36
+ * list (`List.notes`) with the same `id`. Numbers are never stored: they come from the order of the references.
37
+ */
38
+ export interface Note {
39
+ readonly type: "note";
40
+ readonly id: string;
41
+ }
42
+ export type Inline = TextRun | LineBreak | Image | Note;
43
+ /** What an id of a note may be. Anything else is replaced when read, so an id is always safe in an attribute. */
44
+ export declare const NOTE_ID: RegExp;
35
45
  export type Align = "left" | "center" | "right" | "justify";
36
46
  /** The most a block can be indented, in steps. */
37
47
  export declare const MAX_INDENT = 8;
@@ -56,17 +66,30 @@ export interface Heading {
56
66
  }
57
67
  export interface ListItem {
58
68
  readonly content: readonly Inline[];
59
- readonly sublists: readonly List[];
69
+ /**
70
+ * What follows the item's own text, in order: nested lists, and also paragraphs, headings, code, quotes and rules. A paragraph
71
+ * that follows the text with nothing between is not a child but a paragraph break inside `content` (`br` with `paragraph`);
72
+ * a paragraph is a child only after another child (text, code, then more text).
73
+ */
74
+ readonly children: readonly ItemChild[];
60
75
  /** Present when the item is a task: whether it is done. Absent for an ordinary item. */
61
76
  readonly checked?: boolean;
77
+ /** In a notes list: the id of the reference this note belongs to. */
78
+ readonly id?: string;
62
79
  }
80
+ /** What a list item may hold besides its own text. Tables and definition lists are not allowed in an item. */
81
+ export type ItemChild = List | Paragraph | Heading | CodeBlock | Quote | Rule;
63
82
  export interface List {
64
83
  readonly type: "ul" | "ol";
65
84
  readonly start?: number;
66
85
  /** A CSS list-style-type keyword such as `circle` or `lower-alpha`. Absent means the default for the depth. */
67
86
  readonly style?: string;
87
+ /** The footnotes of the document: a numbered list whose items are the notes, kept in the order of their references. */
88
+ readonly notes?: true;
68
89
  readonly items: readonly ListItem[];
69
90
  }
91
+ /** The marker a list has when it names none: numbers for an ordered list, and a bullet by depth for the rest. */
92
+ export declare const DEFAULT_MARKER: (type: "ul" | "ol", depth: number) => string;
70
93
  export interface DefinitionEntry {
71
94
  /** A term (`dt`) or the detail that describes it (`dd`). */
72
95
  readonly kind: "term" | "detail";
package/dist/model.js CHANGED
@@ -1,4 +1,6 @@
1
+ export const NOTE_ID = /^[A-Za-z0-9_-]{1,40}$/;
1
2
  export const MAX_INDENT = 8;
3
+ export const DEFAULT_MARKER = (type, depth) => (type === "ol" ? "decimal" : ["disc", "circle", "square"][Math.min(depth, 2)]);
2
4
  export const ROW_HEIGHT = /^\d{1,4}(\.\d{1,2})?px$/;
3
5
  export const COLOR_VALUE = /^(#[0-9a-f]{3,8}|[a-z]{3,20}|(?:rgb|hsl)a?\([\d\s.,%/-]+\))$/i;
4
6
  export const COLUMN_WIDTH = /^\d{1,5}(\.\d{1,2})?(px|%)$/;
@@ -0,0 +1,6 @@
1
+ import type { Block, Doc, List } from "./model.ts";
2
+ export declare const isNotes: (block: Block | undefined) => block is List & {
3
+ notes: true;
4
+ };
5
+ /** The ids of every reference in the document, in reading order. */
6
+ export declare const noteIds: (doc: Doc) => string[];
package/dist/notes.js ADDED
@@ -0,0 +1,49 @@
1
+ export const isNotes = (block) => block?.type === "ol" && block.notes === true;
2
+ function collect(block, out) {
3
+ const inline = (content) => { for (const n of content)
4
+ if (n.type === "note")
5
+ out.push(n.id); };
6
+ switch (block.type) {
7
+ case "p":
8
+ case "h":
9
+ inline(block.content);
10
+ break;
11
+ case "ul":
12
+ case "ol":
13
+ for (const item of block.items) {
14
+ inline(item.content);
15
+ for (const child of item.children)
16
+ collect(child, out);
17
+ }
18
+ break;
19
+ case "dl":
20
+ for (const entry of block.entries)
21
+ inline(entry.content);
22
+ break;
23
+ case "table":
24
+ for (const row of block.rows)
25
+ for (const cell of row)
26
+ for (const inner of cell.blocks)
27
+ collect(inner, out);
28
+ break;
29
+ case "quote":
30
+ for (const inner of block.blocks)
31
+ collect(inner, out);
32
+ break;
33
+ default: break;
34
+ }
35
+ }
36
+ const found = new WeakMap();
37
+ const NONE = [];
38
+ function idsOf(block) {
39
+ if (isNotes(block))
40
+ return NONE;
41
+ let ids = found.get(block);
42
+ if (!ids) {
43
+ const out = [];
44
+ collect(block, out);
45
+ found.set(block, (ids = out.length === 0 ? NONE : out));
46
+ }
47
+ return ids;
48
+ }
49
+ export const noteIds = (doc) => doc.blocks.flatMap(idsOf);
package/dist/ops.d.ts CHANGED
@@ -3,6 +3,7 @@ import { deleteBackward, deleteForward, deleteSelection, extractSelection, inser
3
3
  import { exportBlock, exportHtml, exportInline } from "./export.ts";
4
4
  import { importBlocks, importHtml, importText } from "./import.ts";
5
5
  import { setLink } from "./links.ts";
6
+ import { isNotes, noteIds } from "./notes.ts";
6
7
  import { decodeBase64, encodeBase64 } from "./base64.ts";
7
8
  import { escapeAttribute, escapeText } from "./html/entities.ts";
8
9
  import { checkImageSrc, parseStyle } from "./import.ts";
@@ -10,8 +11,11 @@ import { imageSelection, selectedImage } from "./imageselect.ts";
10
11
  import { sameLine, sidesOf, withSides } from "./cellborder.ts";
11
12
  import { extent, fromGrid, inRow, locateCell, meets, selectedCells, selectedRect, toGrid } from "./tablegrid.ts";
12
13
  import { inlineLength, inlineText, normalizeInline } from "./model.ts";
13
- import { caret, clampPos, containerPaths, fixup, getBlock, getContent, isCollapsed, kindAt, mapBlockList, pathKey, selectedContainers, setContent } from "./path.ts";
14
+ import { asChildren, caret, clampPos, containerPaths, fixup, getBlock, getContent, isCollapsed, kindAt, mapBlockList, pathKey, selectedContainers, setContent } from "./path.ts";
14
15
  export declare const ops: {
16
+ readonly isNotes: typeof isNotes;
17
+ readonly noteIds: typeof noteIds;
18
+ readonly asChildren: typeof asChildren;
15
19
  readonly caret: typeof caret;
16
20
  readonly clampPos: typeof clampPos;
17
21
  readonly containerPaths: typeof containerPaths;
@@ -28,6 +32,7 @@ export declare const ops: {
28
32
  readonly inlineText: typeof inlineText;
29
33
  readonly normalizeInline: typeof normalizeInline;
30
34
  readonly sliceInline: typeof sliceInline;
35
+ readonly NOTE_ID: RegExp;
31
36
  readonly COLUMN_WIDTH: RegExp;
32
37
  readonly COLOR_VALUE: RegExp;
33
38
  readonly ROW_HEIGHT: RegExp;
package/dist/ops.js CHANGED
@@ -3,17 +3,18 @@ import { deleteBackward, deleteForward, deleteSelection, extractSelection, inser
3
3
  import { exportBlock, exportHtml, exportInline } from "./export.js";
4
4
  import { importBlocks, importHtml, importText } from "./import.js";
5
5
  import { setLink } from "./links.js";
6
+ import { isNotes, noteIds } from "./notes.js";
6
7
  import { decodeBase64, encodeBase64 } from "./base64.js";
7
8
  import { escapeAttribute, escapeText } from "./html/entities.js";
8
9
  import { checkImageSrc, parseStyle } from "./import.js";
9
10
  import { imageSelection, selectedImage } from "./imageselect.js";
10
11
  import { sameLine, SIDES, sidesOf, withSides } from "./cellborder.js";
11
12
  import { extent, fromGrid, inRow, locateCell, meets, selectedCells, selectedRect, toGrid } from "./tablegrid.js";
12
- import { COLOR_VALUE, COLUMN_WIDTH, ROW_HEIGHT, inlineLength, inlineText, normalizeInline } from "./model.js";
13
- import { caret, clampPos, containerPaths, fixup, getBlock, getContent, isCollapsed, kindAt, mapBlockList, pathKey, selectedContainers, setContent, } from "./path.js";
13
+ import { NOTE_ID, COLOR_VALUE, COLUMN_WIDTH, ROW_HEIGHT, inlineLength, inlineText, normalizeInline } from "./model.js";
14
+ import { asChildren, caret, clampPos, containerPaths, fixup, getBlock, getContent, isCollapsed, kindAt, mapBlockList, pathKey, selectedContainers, setContent, } from "./path.js";
14
15
  export const ops = {
15
- caret, clampPos, containerPaths, selectedContainers, getContent, setContent, getBlock, kindAt, mapBlockList, fixup, pathKey, isCollapsed,
16
- inlineLength, inlineText, normalizeInline, sliceInline, COLUMN_WIDTH, COLOR_VALUE, ROW_HEIGHT, imageSelection, selectedImage, sameLine, SIDES, sidesOf, withSides,
16
+ isNotes, noteIds, asChildren, caret, clampPos, containerPaths, selectedContainers, getContent, setContent, getBlock, kindAt, mapBlockList, fixup, pathKey, isCollapsed,
17
+ inlineLength, inlineText, normalizeInline, sliceInline, NOTE_ID, COLUMN_WIDTH, COLOR_VALUE, ROW_HEIGHT, imageSelection, selectedImage, sameLine, SIDES, sidesOf, withSides,
17
18
  toGrid, fromGrid, inRow, extent, meets, selectedRect, selectedCells, locateCell,
18
19
  parseStyle, checkImageSrc, escapeText, escapeAttribute, encodeBase64, decodeBase64,
19
20
  insertText, insertBlocks, insertLineBreak, splitBlock, deleteSelection, deleteBackward, deleteForward, moveSelection,
package/dist/path.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Block, type Doc, type Inline, type List } from "./model.ts";
1
+ import { type Block, type Doc, type Inline, type ItemChild, type List } from "./model.ts";
2
2
  export type Path = readonly number[];
3
3
  export interface Pos {
4
4
  readonly path: Path;
@@ -35,6 +35,8 @@ export declare function kindAt(doc: Doc, path: Path): "p" | "h" | "pre" | "li" |
35
35
  export declare const setContent: (doc: Doc, path: Path, content: readonly Inline[]) => Doc;
36
36
  /** Replaces the block that holds a container (a paragraph, heading or code block) in its parent list of blocks. */
37
37
  export declare function mapBlockList(doc: Doc, path: Path, fn: (blocks: readonly Block[], index: number) => readonly Block[]): Doc;
38
+ /** Blocks as list item children: a table or definition list cannot sit in an item, so its text stays as paragraphs. */
39
+ export declare function asChildren(blocks: readonly Block[]): ItemChild[];
38
40
  /** The block at a path that ends at a block: `[b]`, `[b, n]` inside a quote, `[b, r, c, n]` inside a table cell. */
39
41
  export declare function getBlock(doc: Doc, path: Path): Block | undefined;
40
42
  /** True when the container is a direct child of a list of blocks (so blocks can be inserted beside it). */
package/dist/path.js CHANGED
@@ -14,7 +14,7 @@ export function comparePath(a, b) {
14
14
  function listPaths(list, prefix, out) {
15
15
  list.items.forEach((item, i) => {
16
16
  out.push([...prefix, i]);
17
- item.sublists.forEach((sub, k) => listPaths(sub, [...prefix, i, k], out));
17
+ item.children.forEach((child, k) => blockPaths(child, [...prefix, i, k], out));
18
18
  });
19
19
  }
20
20
  function blockPaths(block, prefix, out) {
@@ -83,8 +83,8 @@ function slotInList(list, rest) {
83
83
  return undefined;
84
84
  if (k === undefined)
85
85
  return { content: item.content, item };
86
- const sub = item.sublists[k];
87
- return sub ? slotInList(sub, more) : undefined;
86
+ const child = item.children[k];
87
+ return child ? slotInBlock(child, more) : undefined;
88
88
  }
89
89
  function slotInBlocks(blocks, rest) {
90
90
  const [n, ...more] = rest;
@@ -114,7 +114,7 @@ function updateList(list, rest, content) {
114
114
  return item;
115
115
  if (k === undefined)
116
116
  return { ...item, content };
117
- return { ...item, sublists: item.sublists.map((sub, s) => (s === k ? updateList(sub, more, content) : sub)) };
117
+ return { ...item, children: item.children.map((child, s) => (s === k ? updateBlock(child, more, content) : child)) };
118
118
  });
119
119
  return { ...list, items };
120
120
  }
@@ -143,6 +143,28 @@ function updateBlocks(blocks, rest, content) {
143
143
  }
144
144
  export const setContent = (doc, path, content) => ({ blocks: updateBlocks(doc.blocks, path, content) });
145
145
  export function mapBlockList(doc, path, fn) {
146
+ const applyList = (list, rest) => {
147
+ const [i, k, ...more] = rest;
148
+ if (i === undefined || k === undefined)
149
+ return list;
150
+ return { ...list, items: list.items.map((item, idx) => (idx !== i ? item : { ...item, children: applyChildren(item.children, [k, ...more]) })) };
151
+ };
152
+ const applyChildren = (children, rest) => {
153
+ const [k, ...more] = rest;
154
+ if (k === undefined)
155
+ return [...children];
156
+ if (more.length === 0)
157
+ return asChildren(fn(children, k));
158
+ return children.map((child, idx) => {
159
+ if (idx !== k)
160
+ return child;
161
+ if (child.type === "ul" || child.type === "ol")
162
+ return applyList(child, more);
163
+ if (child.type === "quote")
164
+ return { ...child, blocks: apply(child.blocks, more) };
165
+ return child;
166
+ });
167
+ };
146
168
  const apply = (blocks, rest) => {
147
169
  const [n, ...more] = rest;
148
170
  if (n === undefined)
@@ -154,6 +176,8 @@ export function mapBlockList(doc, path, fn) {
154
176
  return block;
155
177
  if (block.type === "quote")
156
178
  return { ...block, blocks: apply(block.blocks, more) };
179
+ if (block.type === "ul" || block.type === "ol")
180
+ return applyList(block, more);
157
181
  if (block.type === "table") {
158
182
  const [r, c, ...inner] = more;
159
183
  return { ...block, rows: block.rows.map((row, ri) => (ri !== r ? row : row.map((cell, ci) => (ci !== c ? cell : { ...cell, blocks: apply(cell.blocks, inner) })))) };
@@ -163,6 +187,15 @@ export function mapBlockList(doc, path, fn) {
163
187
  };
164
188
  return { blocks: apply(doc.blocks, path) };
165
189
  }
190
+ export function asChildren(blocks) {
191
+ return blocks.flatMap((block) => {
192
+ switch (block.type) {
193
+ case "table": return block.rows.flatMap((row) => row.flatMap((cell) => asChildren(cell.blocks)));
194
+ case "dl": return block.entries.map((entry) => ({ type: "p", content: entry.content }));
195
+ default: return [block];
196
+ }
197
+ });
198
+ }
166
199
  export function getBlock(doc, path) {
167
200
  const find = (blocks, rest) => {
168
201
  const [n, ...more] = rest;
@@ -171,6 +204,8 @@ export function getBlock(doc, path) {
171
204
  return block;
172
205
  if (block.type === "quote")
173
206
  return find(block.blocks, more);
207
+ if (block.type === "ul" || block.type === "ol")
208
+ return findInList(block, more);
174
209
  if (block.type === "table") {
175
210
  const [r, c, ...inner] = more;
176
211
  const cell = r === undefined || c === undefined ? undefined : block.rows[r]?.[c];
@@ -178,6 +213,17 @@ export function getBlock(doc, path) {
178
213
  }
179
214
  return undefined;
180
215
  };
216
+ const findInList = (list, rest) => {
217
+ const [i, k, ...inner] = rest;
218
+ const child = i === undefined || k === undefined ? undefined : list.items[i]?.children[k];
219
+ if (!child)
220
+ return undefined;
221
+ if (inner.length === 0)
222
+ return child;
223
+ if (child.type === "ul" || child.type === "ol")
224
+ return findInList(child, inner);
225
+ return child.type === "quote" ? find(child.blocks, inner) : undefined;
226
+ };
181
227
  return find(doc.blocks, path);
182
228
  }
183
229
  export function inBlockList(doc, path) {
@@ -193,7 +239,18 @@ export function mapListOf(doc, path, fn) {
193
239
  return fn(list, i);
194
240
  return {
195
241
  ...list,
196
- items: list.items.map((item, idx) => idx !== i ? item : { ...item, sublists: item.sublists.flatMap((sub, s) => (s === k ? [apply(sub, more)].filter((x) => x !== undefined) : [sub])) }),
242
+ items: list.items.map((item, idx) => idx !== i ? item : {
243
+ ...item,
244
+ children: item.children.flatMap((sub, s) => {
245
+ if (s !== k)
246
+ return [sub];
247
+ if (sub.type === "ul" || sub.type === "ol") {
248
+ const next = apply(sub, more);
249
+ return next ? [next] : [];
250
+ }
251
+ return sub.type === "quote" ? [{ ...sub, blocks: walk(sub.blocks, more) }] : [sub];
252
+ }),
253
+ }),
197
254
  };
198
255
  };
199
256
  const walk = (blocks, rest) => {
@@ -226,10 +283,27 @@ export function fixup(doc) {
226
283
  if (block.type === "table") {
227
284
  return [{ ...block, rows: block.rows.map((row) => row.map((cell) => ({ ...cell, blocks: fixBlocks(cell.blocks, true) }))) }];
228
285
  }
286
+ if (block.type === "ul" || block.type === "ol")
287
+ return [fixList(block)];
229
288
  return [block];
230
289
  });
231
290
  return out.length === 0 && needOne ? [{ type: "p", content: [] }] : out;
232
291
  };
292
+ const fixList = (list) => ({
293
+ ...list,
294
+ items: list.items.map((item) => ({
295
+ ...item,
296
+ children: item.children.flatMap((child) => {
297
+ if (child.type === "ul" || child.type === "ol")
298
+ return [fixList(child)];
299
+ if (child.type === "quote") {
300
+ const inner = fixBlocks(child.blocks, false);
301
+ return inner.length > 0 ? [{ ...child, blocks: inner }] : [];
302
+ }
303
+ return [child];
304
+ }),
305
+ })),
306
+ });
233
307
  return { blocks: fixBlocks(doc.blocks, true) };
234
308
  }
235
309
  export function clampPos(doc, pos) {
@@ -258,6 +332,8 @@ export function cellChain(doc, path) {
258
332
  return;
259
333
  if (block.type === "quote")
260
334
  walk(block.blocks, more, [...prefix, n]);
335
+ else if (block.type === "ul" || block.type === "ol")
336
+ walkList(block, more, [...prefix, n]);
261
337
  else if (block.type === "table") {
262
338
  const [r, c, ...inner] = more;
263
339
  if (r === undefined || c === undefined)
@@ -268,6 +344,16 @@ export function cellChain(doc, path) {
268
344
  walk(cell.blocks, inner, [...prefix, n, r, c]);
269
345
  }
270
346
  };
347
+ const walkList = (list, rest, prefix) => {
348
+ const [i, k, ...inner] = rest;
349
+ const child = i === undefined || k === undefined ? undefined : list.items[i]?.children[k];
350
+ if (i === undefined || k === undefined || !child)
351
+ return;
352
+ if (child.type === "ul" || child.type === "ol")
353
+ walkList(child, inner, [...prefix, i, k]);
354
+ else if (child.type === "quote")
355
+ walk(child.blocks, inner, [...prefix, i, k]);
356
+ };
271
357
  walk(doc.blocks, path, []);
272
358
  return chain;
273
359
  }
package/dist/plugin.d.ts CHANGED
@@ -3,13 +3,58 @@ import type { CommandDef, Unsubscribe } from "./commands.ts";
3
3
  import type { Engine } from "./engine.ts";
4
4
  import type { ImportOptions } from "./import.ts";
5
5
  import type { KeyBinding } from "./keymap.ts";
6
- import type { Block } from "./model.ts";
6
+ import type { Block, Doc } from "./model.ts";
7
+ import type { EditResult, Pos, Selection } from "./path.ts";
7
8
  import type { UiItem, UiSlot } from "./ui.ts";
8
9
  import type { Ops } from "./ops.ts";
9
10
  import type { PasteHandler } from "./paste.ts";
10
11
  export declare const PLUGIN_API_VERSION = 1;
11
12
  /** Looks at blocks about to be pasted and returns the blocks to insert. May take time (an upload); then the paste waits. */
12
13
  export type PasteTransform = (blocks: readonly Block[]) => readonly Block[] | Promise<readonly Block[]>;
14
+ /**
15
+ * Gives the formats a copy, cut or drag writes besides the editor's own HTML and plain text: `{ "text/markdown": "..." }`.
16
+ * Return a format the editor already writes (`text/plain`, `text/html`) to replace it. Return undefined to add nothing.
17
+ * Called once per copy with the selected blocks; it must be quick and must not change the document.
18
+ */
19
+ export type CopyFormatter = (blocks: readonly Block[]) => Readonly<Record<string, string>> | undefined;
20
+ /**
21
+ * Puts right what an edit left, such as a footnote whose reference was deleted. Runs after every edit that changes the document, on
22
+ * the result, and returns the document and selection to use; the repair is part of the same undo step. It must be quick, and
23
+ * must return the same document when there is nothing to put right.
24
+ */
25
+ export type Normalizer = (doc: Doc, selection: Selection) => EditResult;
26
+ /** What changed in the editor, as the page-facing part of a plugin hears of it. */
27
+ export interface ViewChange {
28
+ readonly docChanged: boolean;
29
+ readonly selectionChanged: boolean;
30
+ /** The editor gained or lost the focus. */
31
+ readonly focusMoved: boolean;
32
+ }
33
+ /**
34
+ * What the browser view gives the page-facing part of a plugin. Types that name the page (`HTMLElement`, `Node`) are written
35
+ * as `object` here so this file stays free of the DOM; the plugin's view file knows what they are.
36
+ */
37
+ export interface ViewHost {
38
+ /** The element the editor is drawn in (an `HTMLElement`). */
39
+ readonly element: object;
40
+ readonly engine: Engine;
41
+ readonly ops: Ops;
42
+ /** Adds an event listener that is taken away again when the editor or the plugin is. */
43
+ listen(target: object, type: string, handler: (event: never) => void): void;
44
+ /** Called after the page was changed by the plugin itself, so the editor does not take it for a change from outside. */
45
+ quiet(): void;
46
+ /** Called after the editor has drawn each change. */
47
+ onChange(listener: (change: ViewChange) => void): Unsubscribe;
48
+ /** The position in the document for a place in the page (`Node`, offset), or null. */
49
+ posFromDom(node: object, offset: number): Pos | null;
50
+ /** The place in the page (`Node`, offset) for a position in the document, or null. */
51
+ domFromPos(pos: Pos): {
52
+ readonly node: object;
53
+ readonly offset: number;
54
+ } | null;
55
+ }
56
+ /** The page-facing part of a plugin: set up once the editor has a page, returns what takes it away again. */
57
+ export type ViewExtension = (host: ViewHost) => void | Unsubscribe;
13
58
  /** What a plugin needs of a file that was pasted or dropped. A browser `File` fits. */
14
59
  export interface BlobLike {
15
60
  readonly type: string;
@@ -32,6 +77,10 @@ export interface PluginHost {
32
77
  command<A, V>(def: CommandDef<A, V>): void;
33
78
  keybinding(binding: KeyBinding): void;
34
79
  pasteHandler(handler: PasteHandler): void;
80
+ /** Adds a repair that runs after every edit that changes the document. */
81
+ normalize(normalizer: Normalizer): void;
82
+ /** Adds formats to what copy, cut and drag write to the clipboard. Later plugins win when two give the same type. */
83
+ copyFormat(formatter: CopyFormatter): void;
35
84
  /** Takes over Enter, Backspace at the start, and paste inside a kind of container the plugin owns. One plugin per kind. */
36
85
  behavior(kind: ContainerKind, behavior: ContainerBehavior): void;
37
86
  /** Watches typed text before it is inserted, to turn a shortcut into something else. */
@@ -51,6 +100,8 @@ export interface Plugin {
51
100
  /** The plugin API versions it supports: `1`, `1.x`, or a range such as `>=1 <3`. A mismatch fails when it is added. */
52
101
  readonly api: string;
53
102
  setup(host: PluginHost): void | Unsubscribe;
103
+ /** Only in a browser: the part that draws and handles things on the page (table handles, say). The engine never calls it. */
104
+ readonly view?: ViewExtension;
54
105
  }
55
106
  /** Whether a version satisfies a range written as `N`, `N.x`, `>=N`, `<N` or a pair like `>=1 <3`. */
56
107
  export declare function satisfiesApi(range: string, version: number): boolean;
@@ -1,9 +1,10 @@
1
1
  import { type AutoformatOptions } from "../autoformat/plugin.ts";
2
2
  import { Editor, type EditorOptions } from "../dom/view.ts";
3
- import { type ImagesOptions } from "../images/plugin.ts";
3
+ import { type FootnotesOptions } from "../footnotes/plugin.ts";
4
+ import { type ImagesOptions } from "../images/index.ts";
4
5
  import { type ListsOptions } from "../lists/plugin.ts";
5
6
  import type { Plugin } from "../plugin.ts";
6
- import { type TablesOptions } from "../tables/plugin.ts";
7
+ import { type TablesOptions } from "../tables/index.ts";
7
8
  /** Which standard plugins to use, and how. */
8
9
  export interface StandardPluginOptions extends ImagesOptions {
9
10
  /** Options for lists, or `false` to leave lists out. */
@@ -14,6 +15,8 @@ export interface StandardPluginOptions extends ImagesOptions {
14
15
  readonly find?: false;
15
16
  /** Options for tables, or `false` to leave tables out. */
16
17
  readonly tables?: TablesOptions | false;
18
+ /** Options for footnotes, or `false` to leave them out. */
19
+ readonly footnotes?: FootnotesOptions | false;
17
20
  /** More plugins, added after the standard ones. */
18
21
  readonly plugins?: readonly Plugin[];
19
22
  }
@@ -1,10 +1,11 @@
1
1
  import { autoformat } from "../autoformat/plugin.js";
2
2
  import { Editor } from "../dom/view.js";
3
3
  import { find } from "../find/plugin.js";
4
- import { images } from "../images/plugin.js";
4
+ import { footnotes } from "../footnotes/plugin.js";
5
+ import { images } from "../images/index.js";
5
6
  import { lists } from "../lists/plugin.js";
6
7
  import { svg } from "../svg/plugin.js";
7
- import { tables } from "../tables/plugin.js";
8
+ import { tables } from "../tables/index.js";
8
9
  import { word } from "../word/plugin.js";
9
10
  export function standardPlugins(options = {}) {
10
11
  const { uploadImage, maxInlineImageBytes, maxImageFileBytes, onImageRejected } = options;
@@ -13,6 +14,7 @@ export function standardPlugins(options = {}) {
13
14
  ...(options.tables === false ? [] : [tables(options.tables)]),
14
15
  ...(options.find === false ? [] : [find()]),
15
16
  ...(options.autoformat === false ? [] : [autoformat(options.autoformat)]),
17
+ ...(options.footnotes === false ? [] : [footnotes(options.footnotes)]),
16
18
  word(),
17
19
  svg(),
18
20
  images({
@@ -25,8 +27,8 @@ export function standardPlugins(options = {}) {
25
27
  ];
26
28
  }
27
29
  export function createEditor(options) {
28
- const { lists: _lists, tables: _tables, find: _find, autoformat: _autoformat, plugins: _plugins, uploadImage: _u, maxInlineImageBytes: _m, maxImageFileBytes: _f, onImageRejected: _r, ...editorOptions } = options;
29
- void [_lists, _tables, _find, _autoformat, _plugins, _u, _m, _f, _r];
30
+ const { lists: _lists, tables: _tables, find: _find, autoformat: _autoformat, footnotes: _footnotes, plugins: _plugins, uploadImage: _u, maxInlineImageBytes: _m, maxImageFileBytes: _f, onImageRejected: _r, ...editorOptions } = options;
31
+ void [_lists, _tables, _find, _autoformat, _footnotes, _plugins, _u, _m, _f, _r];
30
32
  return new Editor({ ...editorOptions, plugins: standardPlugins(options) });
31
33
  }
32
34
  export { Editor };
@@ -1,4 +1,4 @@
1
- import type { Engine } from "../engine.ts";
1
+ import type { ViewHost } from "../plugin.ts";
2
2
  /** The attribute on a cell that a selection covers, for styling. */
3
3
  export declare const SELECTED_CELL = "data-rk-cell-selected";
4
4
  /**
@@ -6,11 +6,12 @@ export declare const SELECTED_CELL = "data-rk-cell-selected";
6
6
  * attributes on the drawn cells, not part of the model, and they are put on again after every change.
7
7
  */
8
8
  export declare class CellMarks {
9
+ private readonly host;
10
+ private marked;
9
11
  private readonly root;
10
12
  private readonly engine;
11
- private readonly quiet;
12
- private marked;
13
- constructor(root: HTMLElement, engine: Engine, quiet: () => void);
13
+ constructor(host: ViewHost);
14
+ private quiet;
14
15
  update(): void;
15
16
  }
16
17
  /**
@@ -19,4 +20,4 @@ export declare class CellMarks {
19
20
  * The bottom edge of a row works the same way through `table.rowHeight`.
20
21
  * Returns what takes it away again.
21
22
  */
22
- export declare function installColumnResize(root: HTMLElement, engine: Engine, listen: (target: EventTarget, type: string, handler: (event: Event) => void) => void, quiet: () => void): void;
23
+ export declare function installColumnResize(host: ViewHost): void;
@@ -1,20 +1,20 @@
1
1
  import { pathKey } from "../path.js";
2
2
  import { inRow, locateCell, selectedCells } from "../tablegrid.js";
3
- import { posFromDom } from "./render.js";
4
3
  export const SELECTED_CELL = "data-rk-cell-selected";
5
4
  const GRAB = 5;
6
5
  const MIN_WIDTH = 24;
7
6
  const MIN_HEIGHT = 16;
8
7
  export class CellMarks {
8
+ host;
9
+ marked = [];
9
10
  root;
10
11
  engine;
11
- quiet;
12
- marked = [];
13
- constructor(root, engine, quiet) {
14
- this.root = root;
15
- this.engine = engine;
16
- this.quiet = quiet;
12
+ constructor(host) {
13
+ this.host = host;
14
+ this.root = host.element;
15
+ this.engine = host.engine;
17
16
  }
17
+ quiet() { this.host.quiet(); }
18
18
  update() {
19
19
  const sel = this.engine.selection;
20
20
  const found = pathKey(sel.anchor.path) === pathKey(sel.focus.path) ? undefined : selectedCells(this.engine.document, sel);
@@ -56,11 +56,15 @@ function rowEdgeAt(root, event) {
56
56
  const box = row.getBoundingClientRect();
57
57
  return event.clientY >= box.bottom - GRAB && event.clientY <= box.bottom + GRAB ? cell : null;
58
58
  }
59
- export function installColumnResize(root, engine, listen, quiet) {
59
+ export function installColumnResize(host) {
60
+ const root = host.element;
61
+ const engine = host.engine;
62
+ const quiet = () => host.quiet();
63
+ const listen = (target, type, handler) => host.listen(target, type, handler);
60
64
  const available = () => !engine.readOnly && engine.commands.list().some((c) => c.name === "table.columnWidth");
61
65
  const cellPath = (cell) => {
62
66
  const inside = cell.querySelector("[data-rte]");
63
- return inside ? posFromDom(root, inside, 0)?.path : undefined;
67
+ return inside ? host.posFromDom(inside, 0)?.path : undefined;
64
68
  };
65
69
  const rowAvailable = () => !engine.readOnly && engine.commands.list().some((c) => c.name === "table.rowHeight");
66
70
  let rowDrag;
@@ -1,7 +1,6 @@
1
- import type { Engine } from "../engine.ts";
1
+ import type { ViewHost } from "../plugin.ts";
2
2
  export declare class TableGrips {
3
- private readonly root;
4
- private readonly engine;
3
+ private readonly host;
5
4
  private readonly grips;
6
5
  private guide;
7
6
  private readonly stops;
@@ -9,7 +8,9 @@ export declare class TableGrips {
9
8
  private table;
10
9
  private path;
11
10
  private touch;
12
- constructor(root: HTMLElement, engine: Engine);
11
+ private readonly root;
12
+ private readonly engine;
13
+ constructor(host: ViewHost);
13
14
  private available;
14
15
  /** The drawn cell the caret is in, and its table, when the table is at the top level. Found from the page, so it costs nothing in a big table. */
15
16
  private find;
@@ -1,11 +1,9 @@
1
1
  import { getBlock } from "../path.js";
2
2
  import { inRow, locateCell, toGrid } from "../tablegrid.js";
3
- import { domFromPos } from "./render.js";
4
3
  const SIZE = 22;
5
4
  const COLOR = "#17408b";
6
5
  export class TableGrips {
7
- root;
8
- engine;
6
+ host;
9
7
  grips = new Map();
10
8
  guide;
11
9
  stops = [];
@@ -13,9 +11,12 @@ export class TableGrips {
13
11
  table;
14
12
  path = [];
15
13
  touch = false;
16
- constructor(root, engine) {
17
- this.root = root;
18
- this.engine = engine;
14
+ root;
15
+ engine;
16
+ constructor(host) {
17
+ this.host = host;
18
+ this.root = host.element;
19
+ this.engine = host.engine;
19
20
  const reposition = () => this.place();
20
21
  window.addEventListener("scroll", reposition, true);
21
22
  window.addEventListener("resize", reposition);
@@ -40,7 +41,7 @@ export class TableGrips {
40
41
  const top = this.root.children[path[0]];
41
42
  if (!(top instanceof HTMLElement) || top.tagName !== "TABLE")
42
43
  return undefined;
43
- const at = domFromPos(this.root, this.engine.selection.focus);
44
+ const at = this.host.domFromPos(this.engine.selection.focus);
44
45
  const from = at ? (at.node instanceof Element ? at.node : at.node.parentElement) : null;
45
46
  const cell = from?.closest("td, th");
46
47
  return cell instanceof HTMLElement && cell.closest("table") === top ? { cell, table: top, path } : undefined;
@@ -1,2 +1,6 @@
1
- export { tables, type TablesOptions } from "./plugin.ts";
1
+ import { type TablesOptions } from "./plugin.ts";
2
+ import type { Plugin } from "../plugin.ts";
3
+ export type { TablesOptions };
4
+ /** The tables plugin, with the part that draws table handles in a browser. (`./plugin.ts` is the same without it.) */
5
+ export declare const tables: (options?: TablesOptions) => Plugin;
2
6
  export { BORDER_PRESETS, borderPreset, cellBackground, cellBorderPreset, rowHeight, setCellBackground, setCellBorder, setRowHeight, columnWidth, setColumnWidth, canMerge, canSplit, deleteColumn, deleteRow, deleteTable, hasHeaderRow, inTable, insertColumn, insertRow, insertTable, makeTable, mergeCells, moveToCell, splitCell, toggleHeaderRow, type InsertTableOptions } from "./tables.ts";
@@ -1,2 +1,4 @@
1
- export { tables } from "./plugin.js";
1
+ import { tables as engineOnly } from "./plugin.js";
2
+ import { tablesView } from "./view.js";
3
+ export const tables = (options) => ({ ...engineOnly(options), view: tablesView });
2
4
  export { BORDER_PRESETS, borderPreset, cellBackground, cellBorderPreset, rowHeight, setCellBackground, setCellBorder, setRowHeight, columnWidth, setColumnWidth, canMerge, canSplit, deleteColumn, deleteRow, deleteTable, hasHeaderRow, inTable, insertColumn, insertRow, insertTable, makeTable, mergeCells, moveToCell, splitCell, toggleHeaderRow } from "./tables.js";