@quillmark/svelte 0.26.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/NOTICE +4 -4
  2. package/README.md +26 -8
  3. package/THEMING.md +40 -4
  4. package/dist/core/address.d.ts +35 -64
  5. package/dist/core/address.js +35 -55
  6. package/dist/core/bloom.d.ts +4 -10
  7. package/dist/core/bloom.js +14 -27
  8. package/dist/core/codec/blocks.d.ts +41 -43
  9. package/dist/core/codec/blocks.js +97 -130
  10. package/dist/core/codec/code.d.ts +7 -10
  11. package/dist/core/codec/code.js +37 -46
  12. package/dist/core/codec/decode.d.ts +19 -23
  13. package/dist/core/codec/decode.js +76 -111
  14. package/dist/core/codec/encode.d.ts +11 -15
  15. package/dist/core/codec/encode.js +60 -67
  16. package/dist/core/codec/field.d.ts +62 -95
  17. package/dist/core/codec/field.js +107 -179
  18. package/dist/core/codec/islands.d.ts +33 -41
  19. package/dist/core/codec/islands.js +76 -86
  20. package/dist/core/codec/lists.d.ts +17 -26
  21. package/dist/core/codec/lists.js +117 -157
  22. package/dist/core/codec/marks.d.ts +4 -3
  23. package/dist/core/codec/marks.js +8 -3
  24. package/dist/core/codec/prose.css +84 -46
  25. package/dist/core/codec/schema.d.ts +18 -23
  26. package/dist/core/codec/schema.js +84 -76
  27. package/dist/core/codec/slash.d.ts +42 -48
  28. package/dist/core/codec/slash.js +0 -0
  29. package/dist/core/codec/table-copy.d.ts +4 -3
  30. package/dist/core/codec/table-copy.js +25 -7
  31. package/dist/core/codec/table-view.d.ts +12 -12
  32. package/dist/core/codec/table-view.js +460 -383
  33. package/dist/core/codec/table.d.ts +94 -61
  34. package/dist/core/codec/table.js +331 -83
  35. package/dist/core/codec/tasks.d.ts +6 -0
  36. package/dist/core/codec/tasks.js +30 -3
  37. package/dist/core/errors.d.ts +15 -30
  38. package/dist/core/errors.js +2 -6
  39. package/dist/core/index.js +7 -12
  40. package/dist/core/lifecycle.d.ts +7 -16
  41. package/dist/core/lifecycle.js +10 -21
  42. package/dist/core/rebind.svelte.d.ts +10 -20
  43. package/dist/core/rebind.svelte.js +15 -27
  44. package/dist/core/rungs.d.ts +14 -11
  45. package/dist/core/rungs.js +17 -19
  46. package/dist/core/teardown.d.ts +4 -6
  47. package/dist/core/teardown.js +7 -10
  48. package/dist/core/theme.css +74 -54
  49. package/dist/index.js +3 -8
  50. package/dist/preset/app.css +71 -0
  51. package/dist/preset/index.css +8 -7
  52. package/dist/preset/recipes.css +19 -164
  53. package/dist/preset/scale.css +28 -18
  54. package/dist/preset/shell.css +157 -0
  55. package/dist/preview/Preview.svelte +8 -0
  56. package/dist/preview/bridge.d.ts +18 -22
  57. package/dist/preview/bridge.js +29 -58
  58. package/dist/preview/controller.d.ts +21 -29
  59. package/dist/preview/controller.js +14 -39
  60. package/dist/preview/geometry.d.ts +13 -34
  61. package/dist/preview/geometry.js +13 -34
  62. package/dist/preview/index.js +4 -16
  63. package/dist/preview/paint.d.ts +3 -3
  64. package/dist/preview/paint.js +28 -68
  65. package/dist/preview/strings.d.ts +1 -1
  66. package/dist/preview/strings.js +2 -14
  67. package/dist/preview/viewport.js +20 -15
  68. package/dist/preview/zoom.d.ts +21 -22
  69. package/dist/preview/zoom.js +53 -30
  70. package/dist/visual/ArrayField.svelte +77 -16
  71. package/dist/visual/BooleanField.svelte +28 -15
  72. package/dist/visual/Card.svelte +66 -51
  73. package/dist/visual/CardControls.svelte +5 -11
  74. package/dist/visual/DateField.svelte +20 -4
  75. package/dist/visual/EnumField.svelte +3 -0
  76. package/dist/visual/FieldHint.svelte +2 -1
  77. package/dist/visual/FormatPopover.svelte +82 -15
  78. package/dist/visual/FormatPopover.svelte.d.ts +4 -0
  79. package/dist/visual/MatrixAdd.svelte +30 -12
  80. package/dist/visual/MatrixField.svelte +24 -16
  81. package/dist/visual/NumberField.svelte +7 -0
  82. package/dist/visual/ObjectField.svelte +4 -4
  83. package/dist/visual/ProseField.svelte +0 -5
  84. package/dist/visual/TipsCard.svelte +11 -10
  85. package/dist/visual/VariantField.svelte +3 -1
  86. package/dist/visual/VisualEditorInner.svelte +19 -3
  87. package/dist/visual/controls.css +7 -2
  88. package/dist/visual/fonts.d.ts +18 -0
  89. package/dist/visual/fonts.js +59 -0
  90. package/dist/visual/icons/nodes.d.ts +0 -5
  91. package/dist/visual/icons/nodes.js +0 -4
  92. package/dist/visual/props.d.ts +14 -1
  93. package/dist/visual/strings.d.ts +7 -0
  94. package/dist/visual/strings.js +5 -2
  95. package/dist/visual/structure.d.ts +43 -87
  96. package/dist/visual/structure.js +42 -83
  97. package/package.json +2 -2
@@ -1,25 +1,17 @@
1
- // List editing: the body leaf's structural commands, over
2
- // `prosemirror-schema-list` primitives. Indent/outdent on Tab, and the three
3
- // structural keys lists redefine: Enter (split, take the marker off an empty middle
4
- // item, give an empty continuation line one, open an empty line above an unnumbered
5
- // one), Backspace (take an item's marker off, or join a block onto the
6
- // list above it) and Delete (the marker off at the seam below, and the two lines
7
- // joined).
1
+ // List editing: the body leaf's structural commands over `prosemirror-schema-list`: Tab
2
+ // indent/outdent and the structural keys Enter, Backspace and Delete.
8
3
  //
9
- // An item's number is its position: no edit writes `start`. Taking a marker off keeps
10
- // the text where it stands, a continuation of the item above, so the items below close
11
- // up by position. `start` is what a writer typed (`N. `, `blocks.ts`) or what arrived
12
- // with a whole list. The one split left is Backspace at the head of a top-level
13
- // continuation line, which takes the line out to full width; the tail counts from 1.
4
+ // An item's number is its position: no edit writes `start`, which is what a writer typed (`N. `,
5
+ // `blocks.ts`) or what arrived with a whole list. Taking a marker off keeps the text where it
6
+ // stands as a continuation of the item above, so the items below close up by position. The one
7
+ // split is Backspace at the head of a top-level continuation line, which takes the line out to full
8
+ // width; the tail counts from 1.
14
9
  //
15
- // Cleanup is command-local, never a global pass: a command joins the boundary it opens
16
- // and `sinkListItem` reuses the previous item's nested list, so no mutation strands a
17
- // node, and two lists of a kind a command leaves touching it joins (`touching.ts`). A
18
- // whole-doc normalizer would instead fuse every adjacent same-type pair, and that
19
- // boundary is load-bearing: `Container.instance` is how `Content` marks one, the
20
- // upstream normalizer keeps it (`tests/codec/list-shapes.test.ts`), and fusing
21
- // renumbers an imported `1, 2, 1` in a region no edit touched. Reads preserve it; only
22
- // an edit normalizes.
10
+ // Cleanup is command-local, never a global pass: a command joins the boundary it opens, and two
11
+ // lists of a kind a command leaves touching join (`touching.ts`). A whole-doc normalizer would fuse
12
+ // every adjacent same-type pair, but that boundary is load-bearing: `Container.instance` is how
13
+ // `Content` marks one (`tests/codec/list-shapes.test.ts`), and fusing renumbers an imported `1, 2,
14
+ // 1` in a region no edit touched.
23
15
  import { NodeRange } from 'prosemirror-model';
24
16
  import { liftListItem, sinkListItem } from 'prosemirror-schema-list';
25
17
  import { EditorState, Selection, TextSelection } from 'prosemirror-state';
@@ -27,12 +19,12 @@ import { canJoin, findWrapping, liftTarget } from 'prosemirror-transform';
27
19
  import { chainCommands, joinTextblockBackward, joinTextblockForward } from 'prosemirror-commands';
28
20
  import { withinNesting } from './nesting.js';
29
21
  import { toggleList } from './list-toggle.js';
30
- import { splitTaskItem } from './tasks.js';
22
+ import { splitTaskItem, toggleTask } from './tasks.js';
31
23
  import { joinAll, joinLists, touching } from './touching.js';
32
- /** The caret's enclosing `list_item` when it sits at the very start of that item's
33
- * first block: the position both structural keys branch on. `null` otherwise
34
- * (mid-text, a non-first block of a multi-paragraph item, a non-empty selection),
35
- * which is where the key keeps its ordinary meaning. */
24
+ /**
25
+ * The caret's enclosing `list_item` when it sits at the very start of that item's first block,
26
+ * where both structural keys branch; `null` otherwise.
27
+ */
36
28
  function atItemStart(state, itemType) {
37
29
  const { $from, empty } = state.selection;
38
30
  if (!empty || $from.parentOffset !== 0)
@@ -50,10 +42,8 @@ function atItemStart(state, itemType) {
50
42
  };
51
43
  }
52
44
  /**
53
- * The textblock at the end of `node`: `node` itself where it is one, else its last
54
- * descendant that is — the block a join across a boundary reaches,
55
- * `joinTextblockBackward` walking to the same one. `null` on an atom (an island, a
56
- * rule), which holds no line to join onto.
45
+ * The textblock at the end of `node`: itself, else its last descendant that is one. `null` on an
46
+ * atom, which holds no line to join onto.
57
47
  */
58
48
  function lastBlockIn(node) {
59
49
  let last = node ?? null;
@@ -62,11 +52,8 @@ function lastBlockIn(node) {
62
52
  return last?.isTextblock ? last : null;
63
53
  }
64
54
  /**
65
- * Enter at the head of an unnumbered line that holds text → an empty unnumbered line
66
- * above it, the caret staying with the text it pushed down: the split a numbered line
67
- * makes at its head, kept to the line. `splitListItem` would split the item there
68
- * instead, turning the line's text into the next item and leaving the empty line
69
- * behind under the marker it had.
55
+ * Enter at the head of an unnumbered line that holds text → an empty unnumbered line above it, the
56
+ * caret staying with the text. `splitListItem` would instead turn the text into the next item.
70
57
  */
71
58
  function lineAbove(itemType, paragraph) {
72
59
  return (state, dispatch) => {
@@ -84,28 +71,23 @@ function lineAbove(itemType, paragraph) {
84
71
  return true;
85
72
  };
86
73
  }
87
- /** Whether `item` holds nothing but one empty textblock: a line with no text and no
88
- * structure under it. */
74
+ /** Whether `item` holds nothing but one empty textblock. */
89
75
  function emptyItem(item) {
90
76
  return (item.childCount === 1 && !!item.firstChild?.isTextblock && item.firstChild.content.size === 0);
91
77
  }
92
78
  /**
93
- * Take the marker off the item whose first block holds the caret's head, the text
94
- * staying where it stands: a later item's blocks become continuation blocks of the
95
- * item above, and a nested first item's become the parent item's, ahead of the rest of
96
- * its list. Nothing splits, so the items below close up by position. A top-level first
97
- * item has no item above to continue and lifts out over the list instead.
79
+ * Take the marker off the item whose first block holds the caret's head, the text staying where it
80
+ * stands: a later item's blocks become continuation blocks of the item above, a nested first item's
81
+ * become the parent item's. Nothing splits, so the items below close up by position. A top-level
82
+ * first item lifts out over the list instead.
98
83
  *
99
- * An empty item is spent rather than kept as an empty continuation line, the caret
100
- * going to the end of what stood above it: the line held nothing, and the second press
101
- * that would join it away is the one a writer meant. `keepEmpty` is Enter's reading,
102
- * where the empty line is the one about to be typed in.
84
+ * An empty item is spent rather than kept as an empty continuation line, the caret going to the end
85
+ * of what stood above it.
103
86
  *
104
- * Declines where the item has nothing above it in an item and is the first block of
105
- * one besides (`list_item(ordered_list(…))`, a pasted shape): there the content would
106
- * stand ahead of no line.
87
+ * Declines where the item is the first block of one with nothing above it in an item
88
+ * (`list_item(ordered_list(…))`, a pasted shape).
107
89
  */
108
- function markerOff(itemType, keepEmpty = false) {
90
+ function markerOff(itemType) {
109
91
  return (state, dispatch) => {
110
92
  const at = atItemStart(state, itemType);
111
93
  if (!at)
@@ -117,7 +99,7 @@ function markerOff(itemType, keepEmpty = false) {
117
99
  const list = $from.node(-2);
118
100
  const itemStart = $from.before(-1);
119
101
  const itemEnd = itemStart + item.nodeSize;
120
- const spend = !keepEmpty && emptyItem(item);
102
+ const spend = emptyItem(item);
121
103
  const tr = state.tr;
122
104
  let into;
123
105
  if (at.itemIndex > 0) {
@@ -149,16 +131,13 @@ function markerOff(itemType, keepEmpty = false) {
149
131
  };
150
132
  }
151
133
  /**
152
- * Backspace at the head of a continuation line in a top-level item → the line out of
153
- * the list to full width, with the item's blocks after it: the second rung of the
154
- * marker coming off, the line losing its indent as it lost its number. The list splits
155
- * there and the tail counts from 1, a number that names its position in the list it now
156
- * is. The press after is the ordinary join onto the list's last line
134
+ * Backspace at the head of a continuation line in a top-level item → the line out of the list to
135
+ * full width, the item's blocks after it: the second rung of the marker coming off. The list splits
136
+ * there and the tail counts from 1. The press after is the ordinary join onto the list's last line
157
137
  * ({@link joinOntoList}).
158
138
  *
159
- * Declines where a sub-list stands below the line in its item, which the line cannot
160
- * leave without orphaning, and in a nested item, whose continuation lines keep the
161
- * ordinary join: there the next rung out is still inside the list.
139
+ * Declines where a sub-list stands below the line in its item, and in a nested item, whose
140
+ * continuation lines keep the ordinary join.
162
141
  */
163
142
  function lineOut(itemType) {
164
143
  return (state, dispatch) => {
@@ -194,11 +173,9 @@ function lineOut(itemType) {
194
173
  };
195
174
  }
196
175
  /**
197
- * Shift-Tab → `liftListItem` in a nested list, the item up one level. A top-level item
198
- * has no level above it inside the list: the key is claimed and does nothing, as Tab is
199
- * on a first item, so a press too many while backing out of a sub-list renumbers
200
- * nothing. Leaving the list is Backspace's, one rung a press. Outside every item the
201
- * key declines.
176
+ * Shift-Tab → `liftListItem` in a nested list. A top-level item has no level above it inside the
177
+ * list: the key is claimed and does nothing, so a press too many while backing out of a sub-list
178
+ * renumbers nothing. Leaving the list is Backspace's. Outside every item the key declines.
202
179
  */
203
180
  function outdent(itemType) {
204
181
  const lift = liftListItem(itemType);
@@ -213,29 +190,36 @@ function outdent(itemType) {
213
190
  };
214
191
  }
215
192
  /**
216
- * Enter on an empty item that has items after it → the marker off, the empty line kept
217
- * as a continuation of the item above: the unnumbered line a writer opens between two
218
- * numbered ones. An empty last item still leaves the list (`liftListItem`, the chain's
219
- * last link), and a first item has no item above to continue.
193
+ * Enter on an empty top-level item that has items after it → the line out of the list to full
194
+ * width, as an empty last item leaves it. The list splits there and the tail counts from 1. A
195
+ * nested middle item is `liftListItem`'s, one level a press.
220
196
  */
221
- function continueAbove(itemType) {
222
- const off = markerOff(itemType, true);
223
- return (state, dispatch) => {
197
+ function lineOutOfMiddle(itemType) {
198
+ const lift = liftListItem(itemType);
199
+ return (state, dispatch, view) => {
224
200
  const at = atItemStart(state, itemType);
225
201
  const { $from } = state.selection;
226
- if (!at || at.itemIndex === 0 || !emptyItem($from.node(-1)))
202
+ if (!at || at.nested || !emptyItem($from.node(-1)))
227
203
  return false;
228
204
  if (at.itemIndex === $from.node(-2).childCount - 1)
229
205
  return false;
230
- return off(state, dispatch);
206
+ return lift(state, dispatch &&
207
+ ((tr) => {
208
+ const $line = tr.selection.$from;
209
+ const tailPos = $line.after();
210
+ const tail = tr.doc.nodeAt(tailPos);
211
+ if (tail?.firstChild?.type === itemType &&
212
+ tail.attrs.start != null &&
213
+ tail.attrs.start !== 1)
214
+ tr.setNodeMarkup(tailPos, null, { ...tail.attrs, start: 1 });
215
+ dispatch(tr);
216
+ }), view);
231
217
  };
232
218
  }
233
219
  /**
234
- * Enter on an empty continuation line, the last block of an item past its first → the
235
- * marker back on: the line becomes an item of its own after the one it continued. In
236
- * the last item of a list it leaves the list instead, standing after it in the list's
237
- * parent, the exit an empty last item takes. Never `liftListItem`, which would lift the
238
- * whole item the line continues and take that item's marker with it.
220
+ * Enter on an empty continuation line, the last block of an item past its first → the marker back
221
+ * on: the line becomes an item after the one it continued. In the last item of a list it leaves the
222
+ * list instead. Never `liftListItem`, which would lift the whole item and take its marker.
239
223
  */
240
224
  function markerOn(itemType) {
241
225
  return (state, dispatch) => {
@@ -265,18 +249,14 @@ function markerOn(itemType) {
265
249
  };
266
250
  }
267
251
  /**
268
- * Backspace at the head of a block that stands right after a list → join its text into
269
- * the list's last line, as two paragraphs join. The base keymap's answer there wraps the
270
- * block into a fresh item instead, which undoes the lift that just took it out of the
271
- * list ({@link liftAtItemStart}) and leaves the key toggling.
252
+ * Backspace at the head of a block right after a list → join its text into the list's last line.
253
+ * The base keymap would wrap the block into a fresh item, undoing the lift ({@link
254
+ * liftAtItemStart}) and leaving the key toggling.
272
255
  *
273
- * Between a fence and prose the text does not join: a fence is not a line to put another
274
- * line on, and the join would retype one side's whole text as the other's (`code.ts`,
275
- * which refuses the same join between siblings). The block moves into the list's last
276
- * item whole instead, a block of that item under its first.
256
+ * Between a fence and prose the text does not join (`code.ts` refuses the same join between
257
+ * siblings): the block moves into the list's last item whole instead.
277
258
  *
278
- * Either way the block was what stood between the list and the one below it, and two
279
- * lists of a kind it leaves touching join (`touching.ts`).
259
+ * Either way two lists of a kind it leaves touching join (`touching.ts`).
280
260
  */
281
261
  function joinOntoList(itemType) {
282
262
  return (state, dispatch, view) => {
@@ -306,11 +286,9 @@ function joinOntoList(itemType) {
306
286
  };
307
287
  }
308
288
  /**
309
- * Backspace or Delete over a selection that spans blocks → the base keymap's delete, and
310
- * the two lists of a kind it leaves touching joined: a selection taking the paragraph
311
- * between two lists takes the restart with it. A seam that touched before the delete
312
- * is one no edit opened, and stays. Declines wherever the delete opens no seam, the base
313
- * keymap's answer standing.
289
+ * Backspace or Delete over a selection that spans blocks → the base keymap's delete, and the two
290
+ * lists of a kind it leaves touching joined. A seam that touched before the delete is one no edit
291
+ * opened, and stays. Declines wherever the delete opens no seam.
314
292
  */
315
293
  const deleteAcross = (state, dispatch) => {
316
294
  const { $from, $to, empty } = state.selection;
@@ -329,17 +307,14 @@ const deleteAcross = (state, dispatch) => {
329
307
  dispatch?.(tr.scrollIntoView());
330
308
  return true;
331
309
  };
332
- /** The boundaries of every node around `$pos`, and `$pos` itself: where a delete ending
333
- * there can leave two blocks meeting. */
310
+ /** The boundaries of every node around `$pos`, and `$pos` itself. */
334
311
  function seamsAround($pos) {
335
312
  const seams = [$pos.pos];
336
313
  for (let depth = $pos.depth; depth > 0; depth--)
337
314
  seams.push($pos.before(depth), $pos.after(depth));
338
315
  return seams;
339
316
  }
340
- /** Whether the caret sits inside a `list_item` at any depth: a quote's paragraph
341
- * inside an item is still an item's, and a caret outside every list is at no seam of
342
- * one. */
317
+ /** Whether the caret sits inside a `list_item` at any depth. */
343
318
  function inItem($pos, itemType) {
344
319
  for (let depth = $pos.depth; depth > 0; depth--) {
345
320
  if ($pos.node(depth).type === itemType)
@@ -348,25 +323,19 @@ function inItem($pos, itemType) {
348
323
  return false;
349
324
  }
350
325
  /**
351
- * Delete at the very end of a block inside an item → the item below's marker off and
352
- * its first line joined onto the caret's, as every editor merges two lines on a forward
353
- * delete: one keystroke, one line. The marker comes off by the Backspace chain's own
354
- * link (`markerOff`), so the item's other blocks, a nested list among them, stay where
355
- * they stood under the joined line; the join is `joinTextblockForward`'s. Backspace at
356
- * the same seam stops at the marker, the one place the two keys differ: Backspace edits
357
- * the block it is pressed in, and Delete the line after the caret.
358
- *
359
- * The caret stays where the key was pressed.
326
+ * Delete at the very end of a block inside an item → the item below's marker off and its first line
327
+ * joined onto the caret's. The marker comes off by the Backspace chain's own link (`markerOff`), so
328
+ * the item's other blocks stay under the joined line; the join is `joinTextblockForward`'s.
329
+ * Backspace at the same seam stops at the marker: Backspace edits the block it is pressed in,
330
+ * Delete the line after the caret. The caret stays where the key was pressed.
360
331
  *
361
- * An empty item below is spent, and nothing after it joins. An empty item at the caret
362
- * is the other way round: its line is the whole of what Delete edits, so the item below
363
- * moves up into it. A fence takes no prose line (`joinOntoList`): across one only the
364
- * marker comes off, and an empty line above one goes alone.
332
+ * An empty item below is spent, nothing after it joining. An empty item at the caret is the other
333
+ * way round: the item below moves up into it. A fence takes no prose line (`joinOntoList`): across
334
+ * one only the marker comes off.
365
335
  *
366
- * A line above a list is the same seam from outside it: the first item's marker comes
367
- * off and its line joins the caret's. Below that, declining wherever the Backspace chain
368
- * does keeps this to the list's own seams: a boundary inside one item stays the base
369
- * keymap's.
336
+ * A line above a list is the same seam from outside: the first item's marker comes off and its line
337
+ * joins the caret's. Declining wherever the Backspace chain does keeps this to the list's own
338
+ * seams.
370
339
  */
371
340
  function deleteBelow(itemType, press) {
372
341
  const off = markerOff(itemType);
@@ -415,19 +384,19 @@ function deleteBelow(itemType, press) {
415
384
  return true;
416
385
  };
417
386
  }
418
- /** A dispatch that keeps the caret where the key was pressed, through whatever edit a
419
- * command made at a seam below it. */
387
+ /**
388
+ * A dispatch that keeps the caret where the key was pressed through an edit made at a seam below
389
+ * it.
390
+ */
420
391
  function keepCaret(state, dispatch) {
421
392
  const pressed = state.selection.from;
422
393
  return (tr) => dispatch(tr.setSelection(TextSelection.create(tr.doc, tr.mapping.map(pressed))));
423
394
  }
424
395
  /**
425
- * Tab on an unnumbered line, a block of an item past its first → that line alone the
426
- * first item of a sub-list of its list's kind, as `- ` or `1. ` typed at its head makes
427
- * it. `sinkListItem` would sink the whole item, its numbered line with it. A sub-list of
428
- * that kind standing right above or below the line in its item takes it in, the wrap
429
- * joining the boundaries it opens (§cleanup). Declined past `MAX_NESTING_DEPTH`, as
430
- * every sink is.
396
+ * Tab on an unnumbered line, a block of an item past its first → that line alone the first item of
397
+ * a sub-list of its list's kind, as `- ` or `1. ` typed at its head makes it. A sub-list of that
398
+ * kind right above or below takes it in, joining the boundaries the wrap opens. Declined past
399
+ * `MAX_NESTING_DEPTH`.
431
400
  */
432
401
  function sinkLine(itemType) {
433
402
  return (state, dispatch) => {
@@ -454,9 +423,10 @@ function sinkLine(itemType) {
454
423
  return true;
455
424
  };
456
425
  }
457
- /** Tab → `sinkListItem`, declined where the sink would nest past `MAX_NESTING_DEPTH`
458
- * (`nesting.ts`). On a first item, with nothing above to sink under, the key is claimed
459
- * and does nothing: a writer indenting a list's first line is still writing the list. */
426
+ /**
427
+ * Tab → `sinkListItem`, declined past `MAX_NESTING_DEPTH` (`nesting.ts`). On a first item the key
428
+ * is claimed and does nothing.
429
+ */
460
430
  function sinkWithin(itemType) {
461
431
  const sink = sinkListItem(itemType);
462
432
  return (state, dispatch) => {
@@ -472,11 +442,9 @@ function sinkWithin(itemType) {
472
442
  };
473
443
  }
474
444
  /**
475
- * Tab on a block standing right after a list, outside every item → the block moves into
476
- * the list's last item, after what that item holds: one step in, under the item above,
477
- * as Tab nests an item. The block keeps its kind, so a paragraph lands as the item's
478
- * unnumbered line. Declined past `MAX_NESTING_DEPTH`, and over a selection that leaves
479
- * the block.
445
+ * Tab on a block right after a list, outside every item → the block moves into the list's last
446
+ * item, keeping its kind. Declined past `MAX_NESTING_DEPTH`, and over a selection that leaves the
447
+ * block.
480
448
  */
481
449
  function sinkIntoList(itemType) {
482
450
  return (state, dispatch) => {
@@ -501,35 +469,26 @@ function sinkIntoList(itemType) {
501
469
  };
502
470
  }
503
471
  /**
504
- * The list bindings for a block-schema leaf: `{}` for the one-line and plain
505
- * schemas, which declare no list nodes.
506
- *
507
- * `Tab` / `Shift-Tab` hold the list link of the body's Tab chain. Surfaces that own
508
- * Tab more locally under the same rule (`prose/canon/VISUAL_EDITOR.md` §Chrome)
509
- * prepend a link ahead of these in `keymap.ts`: a `code_block` takes literal
510
- * indentation (`code.ts`). Where every link declines, the body holds the key
511
- * (`keymap.ts`).
472
+ * The list bindings for a block-schema leaf: `{}` for the one-line and plain schemas, which declare
473
+ * no list nodes.
512
474
  *
513
- * `Mod-Shift-7` / `Mod-Shift-8` are the list toggle (`list-toggle.ts`), numbering or
514
- * bulleting the selection's lines or taking their markers off.
475
+ * `Tab` / `Shift-Tab` hold the list link of the body's Tab chain; surfaces owning Tab more locally
476
+ * prepend a link in `keymap.ts` (`prose/canon/VISUAL_EDITOR.md` §Chrome). `Tab` sinks an unnumbered
477
+ * line alone into a sub-list, any other line its whole item, and a block right after a list into
478
+ * the list's last item. `Mod-Shift-7` / `Mod-Shift-8` are the list toggle (`list-toggle.ts`).
479
+ * `Mod-Enter` ticks the task item around the caret (`toggleTask`).
515
480
  *
516
- * `Tab` sinks an unnumbered line alone into a sub-list, any other line its whole item,
517
- * and a block right after a list into the list's last item.
481
+ * `Enter` is a chain in precedence order: the empty line above an unnumbered one, an empty top-level
482
+ * middle item out of the list, the marker back on an empty continuation line, `splitListItem` (`splitTaskItem` for
483
+ * tasks), then `liftListItem`. The split link splits a non-empty item, and on an empty last item
484
+ * either splits the wrapping item (a nested one, so Enter lifts one level) or bails so the last
485
+ * link lifts a top-level one to a paragraph.
518
486
  *
519
- * `Enter` is a chain in precedence order: the empty line above an unnumbered one, the
520
- * marker off an empty middle item and back on an empty continuation line, then `splitListItem`
521
- * (`splitTaskItem`, which leaves a task item's new half an open task), then
522
- * `liftListItem`. The split link carries two behaviors of its own: it splits a
523
- * non-empty item, and on an empty last item it either splits the wrapping item (a
524
- * nested one, so Enter lifts exactly one level) or bails so the last link lifts a
525
- * top-level one out to a paragraph. `Backspace` takes an item's marker off at its
526
- * start, a top-level continuation line out to full width, and joins a block onto the
527
- * list it stands after, and both keys over a selection spanning blocks join the lists
528
- * the delete leaves touching. `Delete` is the marker off at the seam below with the two lines
529
- * joined, never the line out: a forward delete edits the line after the caret, not its
530
- * indent. An empty line is neither key's here:
531
- * the block link takes it ahead of this chain (`blocks.ts`), the caret's own line
532
- * being a nearer fact than the boundary under it.
487
+ * `Backspace` takes an item's marker off at its start, a top-level continuation line out to full
488
+ * width, and joins a block onto the list it follows; over a selection spanning blocks both keys
489
+ * join the lists the delete leaves touching. `Delete` is the marker off at the seam below with the
490
+ * two lines joined, never the line out. An empty line is neither key's here: the block link takes
491
+ * it first (`blocks.ts`).
533
492
  */
534
493
  export function listKeymap(schema) {
535
494
  const itemType = schema.nodes.list_item;
@@ -542,7 +501,8 @@ export function listKeymap(schema) {
542
501
  'Mod-Shift-8': toggleList(false),
543
502
  Tab: chainCommands(sinkLine(itemType), sinkWithin(itemType), sinkIntoList(itemType)),
544
503
  'Shift-Tab': outdent(itemType),
545
- Enter: chainCommands(lineAbove(itemType, paragraph), continueAbove(itemType), markerOn(itemType), splitTaskItem(itemType), liftListItem(itemType)),
504
+ 'Mod-Enter': toggleTask(itemType),
505
+ Enter: chainCommands(lineAbove(itemType, paragraph), lineOutOfMiddle(itemType), markerOn(itemType), splitTaskItem(itemType), liftListItem(itemType)),
546
506
  Backspace: chainCommands(deleteAcross, markerOff(itemType), lineOut(itemType), joinOntoList(itemType)),
547
507
  Delete: chainCommands(deleteAcross, deleteBelow(itemType, backspace))
548
508
  };
@@ -1,14 +1,15 @@
1
1
  import type { Mark, Schema } from 'prosemirror-model';
2
2
  import type { ContentMark, ContentMarkKind } from '@quillmark/wasm';
3
- /** A PM mark from a content formatting mark, or `null` for an anchor. Exhaustive over
3
+ /** A PM mark from a content formatting mark, or `null` for an anchor, which no PM mark
4
+ * draws (`field.ts` holds it as a decoration). Exhaustive over
4
5
  * the closed mark vocabulary, so a member added upstream is a compile error here rather
5
6
  * than an index into a PM mark this schema does not declare. */
6
7
  export declare function pmMarkFromContent(schema: Schema, m: ContentMark): Mark | null;
7
8
  /**
8
9
  * A content mark descriptor from a PM mark (range-free): the `{ type, … }` half
9
10
  * of a `ContentMark` / `MarkOp`. `em` collapses to `emph` and a link's `href` rides
10
- * out as the `url` its payload spells; the other four are their own content name,
11
- * which is what the cast claims.
11
+ * out as the `url` its payload spells; a font family's `name` is its own, and the
12
+ * other four are their own content name, which is what the cast claims.
12
13
  */
13
14
  export declare function contentDescriptorFromPM(mark: Mark): ContentMarkKind;
14
15
  /**
@@ -1,11 +1,14 @@
1
1
  import { canonicalJson } from './reconcile.js';
2
- /** A PM mark from a content formatting mark, or `null` for an anchor. Exhaustive over
2
+ /** A PM mark from a content formatting mark, or `null` for an anchor, which no PM mark
3
+ * draws (`field.ts` holds it as a decoration). Exhaustive over
3
4
  * the closed mark vocabulary, so a member added upstream is a compile error here rather
4
5
  * than an index into a PM mark this schema does not declare. */
5
6
  export function pmMarkFromContent(schema, m) {
6
7
  switch (m.type) {
7
8
  case 'anchor':
8
9
  return null;
10
+ case 'font_family':
11
+ return schema.marks.font_family.create({ name: m.attrs.name });
9
12
  case 'emph':
10
13
  return schema.marks.em.create();
11
14
  case 'link':
@@ -24,8 +27,8 @@ export function pmMarkFromContent(schema, m) {
24
27
  /**
25
28
  * A content mark descriptor from a PM mark (range-free): the `{ type, … }` half
26
29
  * of a `ContentMark` / `MarkOp`. `em` collapses to `emph` and a link's `href` rides
27
- * out as the `url` its payload spells; the other four are their own content name,
28
- * which is what the cast claims.
30
+ * out as the `url` its payload spells; a font family's `name` is its own, and the
31
+ * other four are their own content name, which is what the cast claims.
29
32
  */
30
33
  export function contentDescriptorFromPM(mark) {
31
34
  const name = mark.type.name;
@@ -33,6 +36,8 @@ export function contentDescriptorFromPM(mark) {
33
36
  return { type: 'emph' };
34
37
  if (name === 'link')
35
38
  return { type: 'link', attrs: { url: mark.attrs.href } };
39
+ if (name === 'font_family')
40
+ return { type: 'font_family', attrs: { name: mark.attrs.name } };
36
41
  return { type: name };
37
42
  }
38
43
  /**