@quillmark/svelte 0.24.0 → 0.25.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 (55) hide show
  1. package/THEMING.md +1 -1
  2. package/dist/core/codec/blocks.d.ts +23 -6
  3. package/dist/core/codec/blocks.js +163 -38
  4. package/dist/core/codec/code.js +3 -8
  5. package/dist/core/codec/field.d.ts +4 -2
  6. package/dist/core/codec/field.js +12 -4
  7. package/dist/core/codec/index.d.ts +2 -0
  8. package/dist/core/codec/index.js +4 -0
  9. package/dist/core/codec/inputrules.js +40 -10
  10. package/dist/core/codec/keymap.js +19 -1
  11. package/dist/core/codec/line-paste.d.ts +7 -0
  12. package/dist/core/codec/line-paste.js +36 -0
  13. package/dist/core/codec/list-move.d.ts +6 -0
  14. package/dist/core/codec/list-move.js +30 -0
  15. package/dist/core/codec/list-paste.d.ts +2 -0
  16. package/dist/core/codec/list-paste.js +142 -0
  17. package/dist/core/codec/list-toggle.d.ts +4 -0
  18. package/dist/core/codec/list-toggle.js +170 -0
  19. package/dist/core/codec/lists.d.ts +22 -15
  20. package/dist/core/codec/lists.js +404 -97
  21. package/dist/core/codec/markdown-paste.d.ts +9 -0
  22. package/dist/core/codec/markdown-paste.js +63 -2
  23. package/dist/core/codec/nesting.d.ts +5 -1
  24. package/dist/core/codec/nesting.js +1 -1
  25. package/dist/core/codec/prose.css +21 -20
  26. package/dist/core/codec/slash.d.ts +2 -1
  27. package/dist/core/codec/slash.js +0 -0
  28. package/dist/core/codec/touching.d.ts +12 -0
  29. package/dist/core/codec/touching.js +33 -0
  30. package/dist/core/codec/undo.d.ts +5 -0
  31. package/dist/core/codec/undo.js +21 -0
  32. package/dist/preview/Preview.svelte +88 -13
  33. package/dist/preview/Preview.svelte.d.ts +2 -2
  34. package/dist/preview/bridge.js +8 -2
  35. package/dist/preview/controller.d.ts +8 -4
  36. package/dist/preview/controller.js +10 -2
  37. package/dist/preview/index.js +1 -1
  38. package/dist/preview/paint.d.ts +6 -2
  39. package/dist/preview/paint.js +67 -56
  40. package/dist/preview/strings.d.ts +2 -0
  41. package/dist/preview/strings.js +4 -2
  42. package/dist/preview/viewport.d.ts +10 -0
  43. package/dist/preview/viewport.js +271 -0
  44. package/dist/preview/zoom.d.ts +41 -0
  45. package/dist/preview/zoom.js +52 -0
  46. package/dist/visual/ArrayField.svelte +24 -4
  47. package/dist/visual/DateField.svelte +26 -13
  48. package/dist/visual/FormatPopover.svelte +41 -2
  49. package/dist/visual/ProseValue.svelte +35 -3
  50. package/dist/visual/ProseValue.svelte.d.ts +7 -0
  51. package/dist/visual/icons/nodes.d.ts +26 -0
  52. package/dist/visual/icons/nodes.js +16 -0
  53. package/dist/visual/strings.d.ts +4 -2
  54. package/dist/visual/strings.js +9 -3
  55. package/package.json +1 -1
@@ -0,0 +1,36 @@
1
+ // A one-line leaf holds one paragraph, so a paste of several lines lands as that one line:
2
+ // the lines joined by a space, as a browser's single-line input joins them, where the
3
+ // schema would keep the first and drop the rest. An element of an array of them is the
4
+ // exception the array asks for (`visual/ProseValue.svelte`): there each line is a row.
5
+ import { Fragment, Slice } from 'prosemirror-model';
6
+ import { Plugin } from 'prosemirror-state';
7
+ /** The lines a slice holds that hold anything, in order. */
8
+ export function pastedLines(slice) {
9
+ const lines = [];
10
+ slice.content.descendants((node) => {
11
+ if (node.isTextblock && node.content.size)
12
+ lines.push(node);
13
+ return !node.isTextblock;
14
+ });
15
+ return lines;
16
+ }
17
+ /** `lines` as the content of one line, a space between each two. */
18
+ export function joinLines(lines, schema) {
19
+ const parts = [];
20
+ for (const line of lines) {
21
+ if (parts.length)
22
+ parts.push(schema.text(' '));
23
+ line.content.forEach((node) => parts.push(node));
24
+ }
25
+ return Fragment.from(parts);
26
+ }
27
+ export function linePastePlugin() {
28
+ return new Plugin({
29
+ props: {
30
+ transformPasted(slice, view) {
31
+ const lines = pastedLines(slice);
32
+ return lines.length > 1 ? new Slice(joinLines(lines, view.state.schema), 0, 0) : slice;
33
+ }
34
+ }
35
+ });
36
+ }
@@ -0,0 +1,6 @@
1
+ import type { Schema } from 'prosemirror-model';
2
+ import type { Command } from 'prosemirror-state';
3
+ /** The items the selection stands in, in the innermost list holding all of them, moved
4
+ * one place up (`-1`) or down (`1`). */
5
+ export declare function moveItems(dir: -1 | 1): Command;
6
+ export declare function listMoveKeymap(schema: Schema): Record<string, Command>;
@@ -0,0 +1,30 @@
1
+ const isList = (node) => node.type.name === 'ordered_list' || node.type.name === 'bullet_list';
2
+ /** The items the selection stands in, in the innermost list holding all of them, moved
3
+ * one place up (`-1`) or down (`1`). */
4
+ export function moveItems(dir) {
5
+ return (state, dispatch) => {
6
+ const { $from, $to } = state.selection;
7
+ const range = $from.blockRange($to, isList);
8
+ if (!range)
9
+ return false;
10
+ const index = dir < 0 ? range.startIndex - 1 : range.endIndex;
11
+ if (index < 0 || index >= range.parent.childCount)
12
+ return false;
13
+ if (!dispatch)
14
+ return true;
15
+ const sibling = range.parent.child(index);
16
+ const size = sibling.nodeSize;
17
+ const tr = state.tr;
18
+ if (dir < 0)
19
+ tr.delete(range.start - size, range.start).insert(range.end - size, sibling);
20
+ else
21
+ tr.delete(range.end, range.end + size).insert(range.start, sibling);
22
+ dispatch(tr.scrollIntoView());
23
+ return true;
24
+ };
25
+ }
26
+ export function listMoveKeymap(schema) {
27
+ if (!schema.nodes.list_item)
28
+ return {};
29
+ return { 'Alt-ArrowUp': moveItems(-1), 'Alt-ArrowDown': moveItems(1) };
30
+ }
@@ -0,0 +1,2 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ export declare function listPastePlugin(): Plugin;
@@ -0,0 +1,142 @@
1
+ // A pasted list takes the numbering of where it lands (CODEC §"Markdown at the edges").
2
+ // An item's number is its position, so a paste counts where it stands: items pasted into
3
+ // a list are that list's next items, of its kind and in its count, and items pasted on
4
+ // the empty line a list was just left by are the same list's. A list pasted on a line of
5
+ // its own keeps the `start` it was copied with, which a writer typed (`lists.ts` writes
6
+ // none) — but a run of items copied from inside a list is copied counting from 1: their
7
+ // numbers were positions in the list they came out of.
8
+ //
9
+ // A line's shape travels with its end, as a word processor's paragraph mark carries its
10
+ // style: text copied from inside one line pastes as text, whatever block it was copied
11
+ // out of, and paragraphs pasted into a numbered line are numbered lines.
12
+ import { Fragment, Slice } from 'prosemirror-model';
13
+ import { Plugin, Selection } from 'prosemirror-state';
14
+ const isList = (node) => !!node && (node.type.name === 'ordered_list' || node.type.name === 'bullet_list');
15
+ /** The one list a slice is, if it is one: whole, or open where a run of its items was
16
+ * copied. */
17
+ function soleList(slice) {
18
+ return slice.content.childCount === 1 && isList(slice.content.firstChild)
19
+ ? slice.content.firstChild
20
+ : null;
21
+ }
22
+ /** The textblock a slice is the inside of: text copied from within one line, the slice
23
+ * open to that line at both ends down a chain of only children. */
24
+ function lineInside(slice) {
25
+ if (slice.openStart === 0 || slice.openStart !== slice.openEnd)
26
+ return null;
27
+ let node = null;
28
+ for (let d = 0, content = slice.content; d < slice.openStart; d++, content = node.content) {
29
+ if (content.childCount !== 1)
30
+ return null;
31
+ node = content.firstChild;
32
+ }
33
+ return node.isTextblock ? node : null;
34
+ }
35
+ /** `content` with the node `depth` levels down its leading edge counting from 1. */
36
+ function fromOne(content, depth) {
37
+ const first = content.firstChild;
38
+ const node = depth === 1
39
+ ? first.type.create({ ...first.attrs, start: 1 }, first.content, first.marks)
40
+ : first.copy(fromOne(first.content, depth - 1));
41
+ return content.replaceChild(0, node);
42
+ }
43
+ /** A new item beside `item`: a task's sibling is a task, and unchecked. */
44
+ const itemAttrs = (item) => ({ checked: item.attrs.checked === null ? null : false });
45
+ /** HTML a ProseMirror view wrote, which states how deep the copy was open at each end. */
46
+ const PM_SLICE = /\sdata-pm-slice="\d+ \d+/;
47
+ export function listPastePlugin() {
48
+ // Whether the paste in flight states its own open depths: a ProseMirror copy does, and
49
+ // a drag inside the view carries its slice whole. Any other source is parsed open as
50
+ // deep as it goes, so a whole item copied off a web page reads as the inside of one
51
+ // line, and only a slice that says so is taken for one. Read once, by the transform.
52
+ let exact = false;
53
+ return new Plugin({
54
+ props: {
55
+ handleDOMEvents: {
56
+ drop: (view) => {
57
+ exact = !!view.dragging;
58
+ return false;
59
+ }
60
+ },
61
+ transformPastedHTML(html) {
62
+ exact = PM_SLICE.test(html);
63
+ return html;
64
+ },
65
+ // A copy whose leading edge stands past an ordered list's first item is a run
66
+ // out of it, and goes out counting from 1; one from the first item keeps the
67
+ // list's `start`, however the selection ends. The copy is the selection's own
68
+ // slice, its leading edge the caret's ancestors from the top, so the list at
69
+ // depth `d` stands `d` levels down it.
70
+ transformCopied(slice, view) {
71
+ const { $from } = view.state.selection;
72
+ let content = slice.content;
73
+ for (let d = 1; d < $from.depth; d++) {
74
+ const list = $from.node(d);
75
+ if (list.type.name === 'ordered_list' && $from.index(d) > 0 && list.attrs.start !== 1)
76
+ content = fromOne(content, d);
77
+ }
78
+ return content === slice.content
79
+ ? slice
80
+ : new Slice(content, slice.openStart, slice.openEnd);
81
+ },
82
+ // The caret's line keeps its shape under text from inside another line: no
83
+ // list, heading or fence comes along to wrap it. Into a line of an item, a list
84
+ // is its items, which join the list the caret is in where a whole list would
85
+ // nest under that line's item. Paragraphs into an item's first line are items
86
+ // beside it, as a word processor numbers each pasted paragraph; a later line of
87
+ // an item is unnumbered, and so are the paragraphs pasted into it.
88
+ transformPasted(slice, view) {
89
+ const line = exact && lineInside(slice);
90
+ exact = false;
91
+ if (line)
92
+ return new Slice(line.content, 0, 0);
93
+ const { $from, $to } = view.state.selection;
94
+ if (!$from.sameParent($to) || !$from.parent.isTextblock || $from.depth < 2)
95
+ return slice;
96
+ if ($from.parent.type.spec.code)
97
+ return slice;
98
+ const item = $from.node(-1);
99
+ if (item.type.name !== 'list_item')
100
+ return slice;
101
+ const list = soleList(slice);
102
+ if (list)
103
+ return new Slice(list.content, Math.max(slice.openStart - 1, 0), Math.max(slice.openEnd - 1, 0));
104
+ const { content } = slice;
105
+ if ($from.index(-1) !== 0 || content.childCount < 2)
106
+ return slice;
107
+ const items = [];
108
+ for (let i = 0; i < content.childCount; i++) {
109
+ const block = content.child(i);
110
+ if (block.type.name !== 'paragraph')
111
+ return slice;
112
+ items.push(item.type.create(itemAttrs(item), block));
113
+ }
114
+ return new Slice(Fragment.from(items), slice.openStart && slice.openStart + 1, slice.openEnd && slice.openEnd + 1);
115
+ },
116
+ // On the empty line right under a list of the same kind, the line Enter on an
117
+ // empty last item leaves, the items are that list's next ones.
118
+ handlePaste(view, _event, slice) {
119
+ const list = soleList(slice);
120
+ const { $from, empty } = view.state.selection;
121
+ if (!list || !empty || !$from.parent.isTextblock || $from.parent.content.size !== 0)
122
+ return false;
123
+ if ($from.parent.type.spec.code)
124
+ return false;
125
+ const above = view.state.doc.resolve($from.before()).nodeBefore;
126
+ if (!isList(above) || above.type !== list.type)
127
+ return false;
128
+ const end = $from.before() - 1;
129
+ const tr = view.state.tr.delete($from.before(), $from.after()).insert(end, list.content);
130
+ try {
131
+ tr.doc.check();
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ tr.setSelection(Selection.near(tr.doc.resolve(end + list.content.size), -1));
137
+ view.dispatch(tr.scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
138
+ return true;
139
+ }
140
+ }
141
+ });
142
+ }
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'prosemirror-state';
2
+ /** Number (`ordered`) or bullet every line of the selection, or, where every one already
3
+ * carries the kind, take those markers off. Declines where the selection holds no line. */
4
+ export declare function toggleList(ordered: boolean): Command;
@@ -0,0 +1,170 @@
1
+ // The list toggle: every line of a selection numbered or bulleted as one gesture, or
2
+ // every one's marker taken off. Mod-Shift-7 and Mod-Shift-8 bind it, the Docs chords
3
+ // (`lists.ts`), and the selection toolbar offers it (`visual/FormatPopover.svelte`).
4
+ //
5
+ // A line is a textblock the selection touches; a fence is no line here, a list item
6
+ // being no place for one to land unasked. A line carries a marker where it opens its
7
+ // item. An item's later line, a continuation, goes with its item where the item's
8
+ // opening line is selected too, and otherwise carries no marker of its own. Where every
9
+ // line carries one of the kind, the toggle takes those markers off, and only those.
10
+ // Otherwise each line becomes the kind: a run of plain lines is wrapped, one item a
11
+ // line, and a run of continuation lines the same way where it stands, the first items
12
+ // of a sub-list under their item, as `- ` or `1. ` at a continuation's head opens one
13
+ // (`blocks.ts` §`openingAnItem`); a list of the other kind is retyped whole, a list
14
+ // being one kind (`blocks.ts` §`listAtItem`); a line already in the kind stays. What it
15
+ // wraps or retypes joins the lists of its kind it leaves touching (`touching.ts`), so
16
+ // the lines of one selection count as one list.
17
+ import { Fragment, NodeRange, Slice } from 'prosemirror-model';
18
+ import { wrapRangeInList } from 'prosemirror-schema-list';
19
+ import { ReplaceAroundStep } from 'prosemirror-transform';
20
+ import { withinNesting } from './nesting.js';
21
+ import { joinAll } from './touching.js';
22
+ /** The textblocks `[from, to]` touches, by position, fences left out. A selection that
23
+ * ends at the very head of a block has selected nothing in it. */
24
+ function linesIn(doc, from, to) {
25
+ const lines = [];
26
+ doc.nodesBetween(from, to, (node, pos) => {
27
+ if (!node.isTextblock)
28
+ return true;
29
+ if (!node.type.spec.code && (from === to || pos + 1 < to))
30
+ lines.push(pos);
31
+ return false;
32
+ });
33
+ return lines;
34
+ }
35
+ function lineAt(doc, pos, itemType) {
36
+ const $at = doc.resolve(pos + 1);
37
+ let item = -1;
38
+ for (let depth = $at.depth - 1; depth > 0 && item < 0; depth--)
39
+ if ($at.node(depth).type === itemType)
40
+ item = depth;
41
+ let opens = item > 0;
42
+ for (let depth = item; opens && depth < $at.depth; depth++)
43
+ opens = $at.index(depth) === 0;
44
+ return { pos, $at, item, opens };
45
+ }
46
+ /** The selection's lines, each with whether its item's opening line is among them. */
47
+ function linesOf(doc, from, to, itemType) {
48
+ const lines = linesIn(doc, from, to).map((pos) => lineAt(doc, pos, itemType));
49
+ const opened = new Set(lines.filter((l) => l.opens).map((l) => l.$at.before(l.item)));
50
+ return lines.map((line) => ({
51
+ ...line,
52
+ covered: line.item > 0 && !line.opens && opened.has(line.$at.before(line.item))
53
+ }));
54
+ }
55
+ /**
56
+ * `prosemirror-schema-list`'s `liftOutOfList`, over a transaction, which upstream does
57
+ * not export: the items of `range` out of their list, their blocks standing where the
58
+ * list stood, the list closed around them. In a sub-list the blocks land in the parent
59
+ * item, the continuation lines a marker coming off leaves (`lists.ts` §`markerOff`).
60
+ * Returns where the list's tail stands, `null` where the items were its last, and
61
+ * `false` where the parent cannot hold the items' blocks.
62
+ */
63
+ function liftOut(tr, range) {
64
+ const list = range.parent;
65
+ for (let pos = range.end, i = range.endIndex - 1; i > range.startIndex; i--) {
66
+ pos -= list.child(i).nodeSize;
67
+ tr.delete(pos - 1, pos + 1);
68
+ }
69
+ const $start = tr.doc.resolve(range.start);
70
+ const item = $start.nodeAfter;
71
+ const atStart = range.startIndex === 0;
72
+ const atEnd = range.endIndex === list.childCount;
73
+ const start = $start.pos;
74
+ const end = start + item.nodeSize;
75
+ const shell = Fragment.from(list.copy(Fragment.empty));
76
+ const step = new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, new Slice((atStart ? Fragment.empty : shell).append(atEnd ? Fragment.empty : shell), atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1);
77
+ if (tr.maybeStep(step).failed)
78
+ return false;
79
+ if (atEnd)
80
+ return null;
81
+ const $list = tr.doc.resolve(range.$from.before(range.depth));
82
+ return $list.posAtIndex($list.index() + (atStart ? 0 : 1) + item.childCount);
83
+ }
84
+ /** Take every marker the selection's lines carry off, the shallowest list first:
85
+ * lifting an item brings its sub-lists up a level, where the next pass reaches them. A
86
+ * list's tail counts from 1 again, a number that names its position in the list it now
87
+ * is (`lists.ts` §`lineOut`). */
88
+ function unlist(tr, type, itemType) {
89
+ for (;;) {
90
+ const { from, to } = tr.selection;
91
+ const marked = linesIn(tr.doc, from, to)
92
+ .map((pos) => lineAt(tr.doc, pos, itemType))
93
+ .filter((l) => l.opens && l.$at.node(l.item - 1).type === type);
94
+ if (!marked.length)
95
+ return true;
96
+ const depth = Math.min(...marked.map((l) => l.item - 1));
97
+ const listPos = marked.find((l) => l.item - 1 === depth).$at.before(depth);
98
+ const indices = marked
99
+ .filter((l) => l.item - 1 === depth && l.$at.before(depth) === listPos)
100
+ .map((l) => l.$at.index(depth));
101
+ const $list = tr.doc.resolve(listPos + 1);
102
+ const range = new NodeRange(tr.doc.resolve($list.posAtIndex(Math.min(...indices))), tr.doc.resolve($list.posAtIndex(Math.max(...indices) + 1)), depth);
103
+ const tail = liftOut(tr, range);
104
+ if (tail === false)
105
+ return false;
106
+ const rest = tail === null ? null : tr.doc.nodeAt(tail);
107
+ if (tail !== null && rest?.attrs.start != null && rest.attrs.start !== 1)
108
+ tr.setNodeMarkup(tail, null, { ...rest.attrs, start: 1 });
109
+ }
110
+ }
111
+ /** Make every line the kind, and join what that leaves touching. */
112
+ function relist(tr, lines, type, paragraph) {
113
+ const changed = [];
114
+ const runs = [];
115
+ for (const line of lines) {
116
+ const { $at, item } = line;
117
+ if (line.covered)
118
+ continue;
119
+ if (line.opens) {
120
+ const pos = $at.before(item - 1);
121
+ if ($at.node(item - 1).type !== type && !changed.some((c) => c.pos === pos)) {
122
+ tr.setNodeMarkup(pos, type, type.name === 'ordered_list' ? { start: 1 } : null);
123
+ changed.push({ pos, step: tr.steps.length });
124
+ }
125
+ continue;
126
+ }
127
+ const end = line.pos + $at.parent.nodeSize;
128
+ const run = runs[runs.length - 1];
129
+ if (run && run.to === line.pos && run.depth === $at.depth - 1)
130
+ run.to = end;
131
+ else
132
+ runs.push({ from: line.pos, to: end, depth: $at.depth - 1 });
133
+ }
134
+ for (const run of runs.reverse()) {
135
+ tr.setBlockType(run.from, run.to, paragraph);
136
+ const range = new NodeRange(tr.doc.resolve(run.from), tr.doc.resolve(run.to), run.depth);
137
+ if (!wrapRangeInList(tr, range, type))
138
+ return false;
139
+ changed.push({ pos: run.from, step: tr.steps.length });
140
+ }
141
+ const seams = [];
142
+ for (const { pos, step } of changed) {
143
+ const at = tr.mapping.slice(step).map(pos);
144
+ const node = tr.doc.nodeAt(at);
145
+ if (node)
146
+ seams.push(at, at + node.nodeSize);
147
+ }
148
+ joinAll(tr, seams);
149
+ return withinNesting(tr.doc);
150
+ }
151
+ /** Number (`ordered`) or bullet every line of the selection, or, where every one already
152
+ * carries the kind, take those markers off. Declines where the selection holds no line. */
153
+ export function toggleList(ordered) {
154
+ return (state, dispatch) => {
155
+ const { list_item: item, paragraph } = state.schema.nodes;
156
+ const type = state.schema.nodes[ordered ? 'ordered_list' : 'bullet_list'];
157
+ if (!item || !paragraph || !type)
158
+ return false;
159
+ const { from, to } = state.selection;
160
+ const lines = linesOf(state.doc, from, to, item);
161
+ if (!lines.length)
162
+ return false;
163
+ const tr = state.tr;
164
+ const off = lines.every((l) => l.covered || (l.opens && l.$at.node(l.item - 1).type === type));
165
+ if (!(off ? unlist(tr, type, item) : relist(tr, lines, type, paragraph)))
166
+ return false;
167
+ dispatch?.(tr.scrollIntoView());
168
+ return true;
169
+ };
170
+ }
@@ -7,21 +7,28 @@ import type { Command } from 'prosemirror-state';
7
7
  * `Tab` / `Shift-Tab` hold the list link of the body's Tab chain. Surfaces that own
8
8
  * Tab more locally under the same rule (`prose/canon/VISUAL_EDITOR.md` §Chrome)
9
9
  * prepend a link ahead of these in `keymap.ts`: a `code_block` takes literal
10
- * indentation (`code.ts`), an island takes cell traversal once added. Outside all
11
- * of them every link returns false and the key is not swallowed: Tab keeps its
12
- * default meaning, which is the body's only keyboard exit and the open seam for a
13
- * shell structural keymap (VISUAL_EDITOR §Settled and open).
10
+ * indentation (`code.ts`). Where every link declines, the body holds the key
11
+ * (`keymap.ts`).
14
12
  *
15
- * `Enter` is a chain in precedence order: the escape-above gesture, then
16
- * `splitListItem` (`splitTaskItem`, which leaves a task item's new half an open task),
17
- * then `liftListItem`. The middle link carries two behaviors of
18
- * its own: it splits a non-empty item, and on an empty item it either splits the
19
- * wrapping item (a nested empty item, so Enter lifts exactly one level) or bails
20
- * so the third link lifts a top-level empty item out to a paragraph. `Backspace` lifts
21
- * an item at its start, and joins a block onto the list it stands after. `Delete` is
22
- * that same chain at the seam below rather than a second table of cases beside it, so
23
- * the two keys cannot come to differ over a seam this link reaches. An empty line is
24
- * not one: the block link takes it ahead of this chain (`blocks.ts`), the caret's own
25
- * line being a nearer fact than the boundary under it.
13
+ * `Mod-Shift-7` / `Mod-Shift-8` are the list toggle (`list-toggle.ts`), numbering or
14
+ * bulleting the selection's lines or taking their markers off.
15
+ *
16
+ * `Tab` sinks an unnumbered line alone into a sub-list, any other line its whole item,
17
+ * and a block right after a list into the list's last item.
18
+ *
19
+ * `Enter` is a chain in precedence order: the empty line above an unnumbered one, the
20
+ * marker off an empty middle item and back on an empty continuation line, then `splitListItem`
21
+ * (`splitTaskItem`, which leaves a task item's new half an open task), then
22
+ * `liftListItem`. The split link carries two behaviors of its own: it splits a
23
+ * non-empty item, and on an empty last item it either splits the wrapping item (a
24
+ * nested one, so Enter lifts exactly one level) or bails so the last link lifts a
25
+ * top-level one out to a paragraph. `Backspace` takes an item's marker off at its
26
+ * start, a top-level continuation line out to full width, and joins a block onto the
27
+ * list it stands after, and both keys over a selection spanning blocks join the lists
28
+ * the delete leaves touching. `Delete` is the marker off at the seam below with the two lines
29
+ * joined, never the line out: a forward delete edits the line after the caret, not its
30
+ * indent. An empty line is neither key's here:
31
+ * the block link takes it ahead of this chain (`blocks.ts`), the caret's own line
32
+ * being a nearer fact than the boundary under it.
26
33
  */
27
34
  export declare function listKeymap(schema: Schema): Record<string, Command>;