@quillmark/svelte 0.2.0 → 0.3.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.
- package/README.md +2 -2
- package/THEMING.md +11 -7
- package/dist/core/address.d.ts +7 -7
- package/dist/core/address.js +6 -6
- package/dist/core/bloom.d.ts +5 -5
- package/dist/core/bloom.js +8 -8
- package/dist/core/codec/anchor.d.ts +1 -1
- package/dist/core/codec/anchor.js +1 -1
- package/dist/core/codec/atoms.d.ts +10 -0
- package/dist/core/codec/atoms.js +78 -0
- package/dist/core/codec/clipboard.d.ts +59 -0
- package/dist/core/codec/clipboard.js +202 -0
- package/dist/core/codec/code.d.ts +1 -1
- package/dist/core/codec/code.js +4 -4
- package/dist/core/codec/decode.d.ts +1 -1
- package/dist/core/codec/decode.js +8 -8
- package/dist/core/codec/encode.d.ts +4 -4
- package/dist/core/codec/encode.js +10 -10
- package/dist/core/codec/field.d.ts +21 -26
- package/dist/core/codec/field.js +33 -32
- package/dist/core/codec/index.js +5 -8
- package/dist/core/codec/inputrules.js +56 -18
- package/dist/core/codec/islands.d.ts +11 -7
- package/dist/core/codec/islands.js +16 -10
- package/dist/core/codec/keymap.d.ts +5 -6
- package/dist/core/codec/keymap.js +8 -7
- package/dist/core/codec/lists.d.ts +3 -3
- package/dist/core/codec/lists.js +32 -13
- package/dist/core/codec/marks.d.ts +1 -1
- package/dist/core/codec/marks.js +1 -1
- package/dist/core/codec/positions.js +1 -1
- package/dist/core/codec/prose.css +230 -215
- package/dist/core/codec/schema.js +4 -4
- package/dist/core/codec/slash.d.ts +39 -41
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +12 -16
- package/dist/core/codec/table-view.js +488 -292
- package/dist/core/codec/table.d.ts +42 -15
- package/dist/core/codec/table.js +70 -26
- package/dist/core/errors.d.ts +5 -5
- package/dist/core/index.js +2 -2
- package/dist/core/lifecycle.js +1 -1
- package/dist/core/rebind.svelte.d.ts +2 -2
- package/dist/core/rebind.svelte.js +4 -4
- package/dist/core/teardown.d.ts +1 -1
- package/dist/core/teardown.js +1 -1
- package/dist/core/theme.css +144 -54
- package/dist/preset/index.css +1 -1
- package/dist/preset/recipes.css +31 -29
- package/dist/preset/scale.css +36 -25
- package/dist/preview/Preview.svelte +12 -10
- package/dist/preview/Preview.svelte.d.ts +3 -3
- package/dist/preview/bridge.js +4 -4
- package/dist/preview/controller.js +3 -3
- package/dist/preview/geometry.d.ts +2 -2
- package/dist/preview/geometry.js +2 -2
- package/dist/preview/index.js +3 -3
- package/dist/preview/overlay.d.ts +1 -1
- package/dist/preview/overlay.js +3 -3
- package/dist/preview/paint.js +15 -17
- package/dist/visual/ArrayField.svelte +80 -66
- package/dist/visual/ArrayField.svelte.d.ts +1 -1
- package/dist/visual/BooleanField.svelte +8 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -2
- package/dist/visual/Card.svelte +151 -325
- package/dist/visual/Card.svelte.d.ts +2 -6
- package/dist/visual/CardControls.svelte +1 -1
- package/dist/visual/DateField.svelte +19 -19
- package/dist/visual/DateField.svelte.d.ts +3 -3
- package/dist/visual/DiagnosticList.svelte +1 -1
- package/dist/visual/EnumField.svelte +16 -16
- package/dist/visual/EnumField.svelte.d.ts +2 -2
- package/dist/visual/Field.svelte +128 -115
- package/dist/visual/Field.svelte.d.ts +1 -1
- package/dist/visual/FieldHint.svelte +24 -42
- package/dist/visual/FieldHint.svelte.d.ts +1 -1
- package/dist/visual/FieldLabel.svelte +8 -8
- package/dist/visual/FormatPopover.svelte +80 -35
- package/dist/visual/NumberField.svelte +5 -5
- package/dist/visual/NumberField.svelte.d.ts +1 -1
- package/dist/visual/ObjectField.svelte +9 -9
- package/dist/visual/ObjectField.svelte.d.ts +1 -1
- package/dist/visual/ProseArrayElement.svelte +11 -13
- package/dist/visual/ProseArrayElement.svelte.d.ts +1 -1
- package/dist/visual/ProseField.svelte +53 -84
- package/dist/visual/ProseField.svelte.d.ts +12 -17
- package/dist/visual/SlashMenu.svelte +42 -16
- package/dist/visual/TextField.svelte +5 -5
- package/dist/visual/TextField.svelte.d.ts +1 -1
- package/dist/visual/TipsCard.svelte +43 -28
- package/dist/visual/VisualEditor.svelte +6 -6
- package/dist/visual/VisualEditorInner.svelte +122 -129
- package/dist/visual/controls.css +74 -30
- package/dist/visual/diagnostics.d.ts +5 -5
- package/dist/visual/diagnostics.js +3 -3
- package/dist/visual/domid.js +3 -3
- package/dist/visual/ext.d.ts +1 -1
- package/dist/visual/ext.js +1 -1
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/index.js +4 -4
- package/dist/visual/leaves.d.ts +3 -3
- package/dist/visual/leaves.js +1 -1
- package/dist/visual/links.d.ts +24 -0
- package/dist/visual/links.js +67 -0
- package/dist/visual/props.d.ts +8 -8
- package/dist/visual/signals.d.ts +6 -6
- package/dist/visual/strings.d.ts +14 -19
- package/dist/visual/strings.js +5 -7
- package/dist/visual/structure.d.ts +24 -17
- package/dist/visual/structure.js +8 -8
- package/dist/visual/synced.svelte.d.ts +1 -1
- package/dist/visual/synced.svelte.js +4 -4
- package/dist/visual/tips.d.ts +2 -2
- package/dist/visual/tips.js +3 -3
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { type Command } from 'prosemirror-state';
|
|
|
6
6
|
*
|
|
7
7
|
* `Enter` is upstream's `newlineInCode`, and it is load-bearing rather than
|
|
8
8
|
* cosmetic: inside a `list_item > code_block` the list link's `splitListItem`
|
|
9
|
-
* otherwise splits the
|
|
9
|
+
* otherwise splits the item in two, each half holding a code block. Every command
|
|
10
10
|
* here declines outside a code block, so the list links keep the keys elsewhere.
|
|
11
11
|
*/
|
|
12
12
|
export declare function codeKeymap(schema: Schema): Record<string, Command>;
|
package/dist/core/codec/code.js
CHANGED
|
@@ -16,7 +16,7 @@ function codeBlockAt(state) {
|
|
|
16
16
|
/**
|
|
17
17
|
* Start offsets of the lines the range `[f, t)` covers, in document order.
|
|
18
18
|
*
|
|
19
|
-
* A line whose start
|
|
19
|
+
* A line whose start is `t` is excluded on a non-empty range: a selection ending at
|
|
20
20
|
* a line start does not reach into that line, the editor convention (else selecting
|
|
21
21
|
* whole lines by dragging to the next line's start indents one line too many).
|
|
22
22
|
*/
|
|
@@ -44,7 +44,7 @@ function outdentWidth(text, s) {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Tab in a code block: insert one indent unit, or indent every covered line when the
|
|
47
|
-
* selection spans a newline. A single-line selection is
|
|
47
|
+
* selection spans a newline. A single-line selection is replaced by the unit: Tab
|
|
48
48
|
* types, the way any other key does.
|
|
49
49
|
*/
|
|
50
50
|
const indentInCode = (state, dispatch) => {
|
|
@@ -75,7 +75,7 @@ const indentInCode = (state, dispatch) => {
|
|
|
75
75
|
/**
|
|
76
76
|
* Shift-Tab in a code block: remove one indent level from every covered line.
|
|
77
77
|
*
|
|
78
|
-
* Declines when no covered line carries an indent, so the key is
|
|
78
|
+
* Declines when no covered line carries an indent, so the key is not swallowed: it
|
|
79
79
|
* falls through to the list link and then out of the leaf, which is the body's
|
|
80
80
|
* keyboard exit from inside a code block (VISUAL_EDITOR §Chrome).
|
|
81
81
|
*/
|
|
@@ -104,7 +104,7 @@ const outdentInCode = (state, dispatch) => {
|
|
|
104
104
|
*
|
|
105
105
|
* `Enter` is upstream's `newlineInCode`, and it is load-bearing rather than
|
|
106
106
|
* cosmetic: inside a `list_item > code_block` the list link's `splitListItem`
|
|
107
|
-
* otherwise splits the
|
|
107
|
+
* otherwise splits the item in two, each half holding a code block. Every command
|
|
108
108
|
* here declines outside a code block, so the list links keep the keys elsewhere.
|
|
109
109
|
*/
|
|
110
110
|
export function codeKeymap(schema) {
|
|
@@ -9,7 +9,7 @@ interface DecodeOpts {
|
|
|
9
9
|
plaintext?: boolean;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* A `Content` as
|
|
12
|
+
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`.
|
|
13
13
|
* The rendering half of the codec's job with no editing attached (no PM view, no
|
|
14
14
|
* plugins, no `contenteditable`) for chrome that must show content in the same
|
|
15
15
|
* mark vocabulary a leaf edits it in (the tips card). A second renderer over the
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// prefix (`list_item`/`quote` → lists/blockquote), select the block node by
|
|
5
5
|
// `kind`, apply marks over their `[start,end)` USV ranges (PM splits inline nodes
|
|
6
6
|
// at mark boundaries), and lower island slots to leaf nodes (inline in a `para`
|
|
7
|
-
// line, block on an `island` line). Anchors are
|
|
7
|
+
// line, block on an `island` line). Anchors are not applied here; they are
|
|
8
8
|
// decorations (field.ts). Positions throughout are USV; `Array.from` iterates by
|
|
9
9
|
// code point so an astral char is one unit, never a surrogate half.
|
|
10
10
|
import { DOMSerializer } from 'prosemirror-model';
|
|
@@ -24,7 +24,7 @@ export function usvLength(s) {
|
|
|
24
24
|
return n;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* A `Content` as
|
|
27
|
+
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`.
|
|
28
28
|
* The rendering half of the codec's job with no editing attached (no PM view, no
|
|
29
29
|
* plugins, no `contenteditable`) for chrome that must show content in the same
|
|
30
30
|
* mark vocabulary a leaf edits it in (the tips card). A second renderer over the
|
|
@@ -92,7 +92,7 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
92
92
|
continue;
|
|
93
93
|
}
|
|
94
94
|
const here = path[depth];
|
|
95
|
-
// `ContentContainer` is an
|
|
95
|
+
// `ContentContainer` is an open set: a bare `here.container === 'x'` does not
|
|
96
96
|
// narrow, so the two arms this schema builds are claimed by the boundary's
|
|
97
97
|
// guard and a literal check, and everything else is an unknown the inert
|
|
98
98
|
// wrapper carries (CODEC §Open sets); no branch degrades silently.
|
|
@@ -106,7 +106,7 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
106
106
|
const c = atDepth(leaves[j], depth);
|
|
107
107
|
if (!c || !isListItemContainer(c) || c.ordered !== ordered || c.start !== start)
|
|
108
108
|
break;
|
|
109
|
-
// An ordinal reset is an
|
|
109
|
+
// An ordinal reset is an adjacent sibling list (content preserves the
|
|
110
110
|
// shape): merging it here would re-encode `[0,1,0]` as `[0,1,2]`,
|
|
111
111
|
// breaking the decode round-trip on the first edit.
|
|
112
112
|
if (c.ordinal < prevOrd)
|
|
@@ -131,7 +131,7 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
131
131
|
continue;
|
|
132
132
|
}
|
|
133
133
|
// quote and every unknown container share one shape: a wrapper over the run
|
|
134
|
-
// of leaves carrying the
|
|
134
|
+
// of leaves carrying the identical container here (identity by `containerKey`,
|
|
135
135
|
// so two adjacent unknowns differing only in `attrs` stay two wrappers).
|
|
136
136
|
const key = containerKey(here);
|
|
137
137
|
let j = i + 1;
|
|
@@ -165,7 +165,7 @@ function containerKey(c) {
|
|
|
165
165
|
return 'attrs' in c ? `${c.container}\u0000${JSON.stringify(c.attrs)}` : c.container;
|
|
166
166
|
}
|
|
167
167
|
/** A single leaf block node (para/heading/code/rule/island, or an unknown kind
|
|
168
|
-
* carried on a paragraph) from its segments. `kind` is an
|
|
168
|
+
* carried on a paragraph) from its segments. `kind` is an open set, so the two
|
|
169
169
|
* payload-carrying arms read through the boundary's guards; the payload-free arms
|
|
170
170
|
* compare literally, and anything left is unknown. */
|
|
171
171
|
function makeLeaf(schema, leaf, marks, cursor) {
|
|
@@ -191,13 +191,13 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
191
191
|
if (isHeadingLine(line)) {
|
|
192
192
|
return schema.nodes.heading.create({ level: line.level }, inline);
|
|
193
193
|
}
|
|
194
|
-
// An unknown kind renders as a paragraph
|
|
194
|
+
// An unknown kind renders as a paragraph and rides on it, so it re-encodes
|
|
195
195
|
// verbatim rather than flattening to `para` on the field's first edit.
|
|
196
196
|
const unknown = line.kind === 'para' ? null : { kind: line.kind, attrs: 'attrs' in line ? line.attrs : null };
|
|
197
197
|
return schema.nodes.paragraph.create({ unknown }, inline);
|
|
198
198
|
}
|
|
199
199
|
/** Consume the next island entry as PM node attrs (text-order matched to slots):
|
|
200
|
-
* the
|
|
200
|
+
* the whole entry, `loss` included, which an island edit has to write back
|
|
201
201
|
* (`islands.ts`). A slot with no entry behind it is a malformed content; the empty
|
|
202
202
|
* attrs keep the decode total rather than throwing on a read. */
|
|
203
203
|
function islandAttrs(cursor) {
|
|
@@ -27,7 +27,7 @@ export interface Scan {
|
|
|
27
27
|
usvEnd: number;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Walk a PM document once, producing the content projection
|
|
30
|
+
* Walk a PM document once, producing the content projection and the position-map
|
|
31
31
|
* runs (they share the traversal so they can never disagree). This is the single
|
|
32
32
|
* source both `pmToContent` and the position map draw from.
|
|
33
33
|
*/
|
|
@@ -40,14 +40,14 @@ interface LowerOpts {
|
|
|
40
40
|
id: string;
|
|
41
41
|
pos: number;
|
|
42
42
|
}[];
|
|
43
|
-
/** Anchor decoration positions after the edit, in
|
|
43
|
+
/** Anchor decoration positions after the edit, in final (new) USV coords. */
|
|
44
44
|
newAnchors?: {
|
|
45
45
|
id: string;
|
|
46
46
|
pos: number;
|
|
47
47
|
}[];
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* One old→new content edit with the text splice it implies.
|
|
50
|
+
* One old→new content edit with the text splice it implies. Both sides are content,
|
|
51
51
|
* never a PM doc: the projection of the new doc (`pmToContent`) is the caller's to
|
|
52
52
|
* hold, because a doc-taking overload re-walks the whole tree, once per keystroke,
|
|
53
53
|
* for a value the caller has in hand.
|
|
@@ -56,7 +56,7 @@ interface LowerOpts {
|
|
|
56
56
|
* and the commit path holds the edit across the lowering. `contentEdit` being its
|
|
57
57
|
* only constructor is what keeps a `delta` from disagreeing with the contents it
|
|
58
58
|
* was diffed from; `lower` is where a slot inside that splice moves to the island
|
|
59
|
-
* channel, so this `delta` is the
|
|
59
|
+
* channel, so this `delta` is the raw splice, not always the one committed.
|
|
60
60
|
*/
|
|
61
61
|
export interface ContentEdit {
|
|
62
62
|
readonly oldRt: Content;
|
|
@@ -10,7 +10,7 @@ function appendText(acc, s) {
|
|
|
10
10
|
acc.usvEnd += usvLength(s);
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Walk a PM document once, producing the content projection
|
|
13
|
+
* Walk a PM document once, producing the content projection and the position-map
|
|
14
14
|
* runs (they share the traversal so they can never disagree). This is the single
|
|
15
15
|
* source both `pmToContent` and the position map draw from.
|
|
16
16
|
*/
|
|
@@ -50,7 +50,7 @@ function scanBlock(acc, node, nodePos, containers) {
|
|
|
50
50
|
case 'paragraph':
|
|
51
51
|
case 'heading': {
|
|
52
52
|
// One kind value per block: the opening line and its hard-break
|
|
53
|
-
// continuations read the
|
|
53
|
+
// continuations read the same object, so they cannot disagree.
|
|
54
54
|
const kind = textblockKind(node);
|
|
55
55
|
beginLine(acc, contentStart, containers, kind);
|
|
56
56
|
scanInline(acc, node, contentStart, containers, kind);
|
|
@@ -256,7 +256,7 @@ function diffLines(oldRt, newRt) {
|
|
|
256
256
|
}
|
|
257
257
|
/** A line minus its `containers` / `continues` envelope: exactly `setKind`'s
|
|
258
258
|
* payload, which the boundary names `ContentLineKind`. Lifting it whole is what
|
|
259
|
-
* keeps this arm-agnostic: `kind` is an
|
|
259
|
+
* keeps this arm-agnostic: `kind` is an open set, so an arm-by-arm switch would
|
|
260
260
|
* have to guess at the unknown arm's payload and would drift on every arm
|
|
261
261
|
* upstream adds. */
|
|
262
262
|
function kindPart(l) {
|
|
@@ -286,14 +286,14 @@ function kindKey(l) {
|
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
288
288
|
* Split the edit's islands off the text splice. `applyChange` throws
|
|
289
|
-
* `IslandSlotInInsert` on a `delta` that types a slot, so a slot
|
|
289
|
+
* `IslandSlotInInsert` on a `delta` that types a slot, so a slot never rides the
|
|
290
290
|
* delta: every slot inside the splice's inserted region is stripped from it and
|
|
291
291
|
* placed by `{ op: 'insert', at }`, which carries the backing entry in the same
|
|
292
292
|
* op. Every other island kept its slot, so a changed payload is `{ op: 'set' }`,
|
|
293
|
-
* addressed by the id both sides carry. A
|
|
293
|
+
* addressed by the id both sides carry. A deleted island needs no op at all: the
|
|
294
294
|
* delta that removes its slot drops the entry.
|
|
295
295
|
*
|
|
296
|
-
* `at` is the slot's position in the
|
|
296
|
+
* `at` is the slot's position in the final text: inserts apply left to right, so
|
|
297
297
|
* by the time the k-th lands, the k slots before it are back in place.
|
|
298
298
|
*
|
|
299
299
|
* The pairing is positional for `insert` and by id for `set`, which is what each
|
|
@@ -338,7 +338,7 @@ function slotPositions(text) {
|
|
|
338
338
|
}
|
|
339
339
|
return out;
|
|
340
340
|
}
|
|
341
|
-
/** The splice's inserted region `[start, end)` in
|
|
341
|
+
/** The splice's inserted region `[start, end)` in final coords. `diffText` emits
|
|
342
342
|
* one `[retain?][delete?][insert?][retain?]`, so the leading retain is where the
|
|
343
343
|
* insert lands and its length is how far it reaches. */
|
|
344
344
|
function insertedRegion(delta) {
|
|
@@ -350,7 +350,7 @@ function insertedRegion(delta) {
|
|
|
350
350
|
return { start, end: start + (insert ? usvLength(insert.insert) : 0) };
|
|
351
351
|
}
|
|
352
352
|
/** The delta with every island slot removed from its insert, or `undefined` when
|
|
353
|
-
* that leaves nothing but retains. Retain/delete arithmetic reads the
|
|
353
|
+
* that leaves nothing but retains. Retain/delete arithmetic reads the old text,
|
|
354
354
|
* so dropping inserted characters leaves the rest of the splice intact. */
|
|
355
355
|
function withoutSlots(delta) {
|
|
356
356
|
const ops = [];
|
|
@@ -375,7 +375,7 @@ function withoutSlots(delta) {
|
|
|
375
375
|
* A post-delta position carried through the bundle's island inserts, which move
|
|
376
376
|
* text exactly as the delta does and rebase marks by the same rule (start-assoc
|
|
377
377
|
* `after`, end-assoc `before`; an anchor is a point and holds `before`). Ascending
|
|
378
|
-
* `at`s apply left to right, so each comparison reads the
|
|
378
|
+
* `at`s apply left to right, so each comparison reads the running position.
|
|
379
379
|
*/
|
|
380
380
|
function shiftPastIslands(pos, insertedAt, assoc) {
|
|
381
381
|
let p = pos;
|
|
@@ -389,7 +389,7 @@ function diffMarks(oldRt, newRt, delta, insertedAt, opts) {
|
|
|
389
389
|
const ops = [];
|
|
390
390
|
// Read the gate once per diff rather than once per mark.
|
|
391
391
|
const { mapPos } = core();
|
|
392
|
-
// Rebase old marks through
|
|
392
|
+
// Rebase old marks through both text-moving channels to final coords, exactly
|
|
393
393
|
// as `applyChange` does internally (start assoc `after`, end assoc `before`).
|
|
394
394
|
const rebase = (pos, assoc) => shiftPastIslands(delta ? mapPos(delta, pos, assoc) : pos, insertedAt, assoc);
|
|
395
395
|
// Group both sides by descriptor key (excluding anchors).
|
|
@@ -4,7 +4,7 @@ import { EditorView } from 'prosemirror-view';
|
|
|
4
4
|
import type { Document, Content, Addr, Quill } from '@quillmark/wasm';
|
|
5
5
|
import type { EditorErrorHandler } from '../errors.js';
|
|
6
6
|
import { type TableChromeStrings } from './table-view.js';
|
|
7
|
-
import { type
|
|
7
|
+
import { type SlashState } from './slash.js';
|
|
8
8
|
/** Options for {@link createField}. */
|
|
9
9
|
export interface CreateFieldOpts {
|
|
10
10
|
doc: Document;
|
|
@@ -20,24 +20,22 @@ export interface CreateFieldOpts {
|
|
|
20
20
|
plaintext?: boolean;
|
|
21
21
|
/** Suppress the markdown-shorthand input rules. */
|
|
22
22
|
noInputRules?: boolean;
|
|
23
|
-
/** The island chrome's wording, read
|
|
23
|
+
/** The island chrome's wording, read live (per render) so a consumer swapping
|
|
24
24
|
* locale mid-session re-renders rather than freezing it at mount. Absent leaves
|
|
25
25
|
* the package's English; an inline leaf never asks (it holds no island). */
|
|
26
26
|
tableStrings?: () => TableChromeStrings;
|
|
27
|
-
/** The slash menu's wording, read live for the same reason. */
|
|
28
|
-
slashStrings?: () => SlashStrings;
|
|
29
27
|
/**
|
|
30
28
|
* The slash menu's live state, or `undefined` when it is closed: the channel the
|
|
31
29
|
* chrome draws from (`visual/SlashMenu.svelte`).
|
|
32
30
|
*
|
|
33
|
-
* Its
|
|
31
|
+
* Its presence is what mounts the menu at all. The trigger and its keys are the
|
|
34
32
|
* leaf's, but a surface only a keyboard can reach (claiming Enter, Escape and the
|
|
35
33
|
* arrows with nothing on screen) is worse than no surface, so the door exists
|
|
36
34
|
* exactly where something can draw it. A constrained inline leaf never has one:
|
|
37
35
|
* it holds no island and no block to convert.
|
|
38
36
|
*/
|
|
39
37
|
onSlash?(state: SlashState | undefined): void;
|
|
40
|
-
/** Accessible name → `aria-label` on the `contenteditable`. For a leaf
|
|
38
|
+
/** Accessible name → `aria-label` on the `contenteditable`. For a leaf nothing
|
|
41
39
|
* else names: an array element (the field label plus its 1-based index), the
|
|
42
40
|
* card body (no visible label at all). A leaf with a field label takes
|
|
43
41
|
* `labelledBy` instead: `for` cannot reach a `contenteditable`, which is not a
|
|
@@ -48,7 +46,7 @@ export interface CreateFieldOpts {
|
|
|
48
46
|
/** The parked `description` → `aria-describedby`; announced after the name. */
|
|
49
47
|
describedBy?: string;
|
|
50
48
|
/** Ghost text shown on the empty leaf: the resolved `default:` a field ghosts,
|
|
51
|
-
* or a body's invitation. The
|
|
49
|
+
* or a body's invitation. The initial value; {@link
|
|
52
50
|
* FieldController.setPlaceholder} moves it after mount. Empty/absent
|
|
53
51
|
* shows no ghost. */
|
|
54
52
|
placeholder?: string;
|
|
@@ -56,7 +54,7 @@ export interface CreateFieldOpts {
|
|
|
56
54
|
/** Fired with the new USV caret after an edit or a selection move. */
|
|
57
55
|
onCaretMove?(addr: Addr, pos: number): void;
|
|
58
56
|
/**
|
|
59
|
-
* Fired after an edit
|
|
57
|
+
* Fired after an edit committed to the document: the leaf's change signal, and
|
|
60
58
|
* the reason it is not `onCaretMove`. That one also fires on a bare selection
|
|
61
59
|
* move, so a host driving a recompile off it recompiles on every arrow key.
|
|
62
60
|
*
|
|
@@ -83,7 +81,7 @@ export interface FieldController {
|
|
|
83
81
|
* its new kind's wording without remounting, which it must not do (the leaf key
|
|
84
82
|
* is the card's session id, so a remount would cost the caret).
|
|
85
83
|
*
|
|
86
|
-
* Refreshes the decoration
|
|
84
|
+
* Refreshes the decoration without a transaction: a placeholder is chrome, and
|
|
87
85
|
* a transaction would fire `onCaretMove` at a moment the caret did not move.
|
|
88
86
|
*/
|
|
89
87
|
setPlaceholder(text: string | undefined): void;
|
|
@@ -103,14 +101,14 @@ export interface FieldController {
|
|
|
103
101
|
/** Ids of the anchors within USV range `[from, to]`: a selection's anchor state. */
|
|
104
102
|
anchorsInRange(from: number, to: number): string[];
|
|
105
103
|
/**
|
|
106
|
-
* Move the slash menu's cursor onto
|
|
107
|
-
* calls. The keyboard cursor and the pointer's highlight are one state, so the
|
|
104
|
+
* Move the slash menu's cursor onto the command `name`: what a pointer entering an
|
|
105
|
+
* item calls. The keyboard cursor and the pointer's highlight are one state, so the
|
|
108
106
|
* chrome moves the one the keys already drive rather than painting a second.
|
|
109
107
|
*/
|
|
110
|
-
slashFocus(
|
|
111
|
-
/** Run
|
|
112
|
-
* no menu open, which is the state a stale click lands in. */
|
|
113
|
-
slashPick(
|
|
108
|
+
slashFocus(name: string): void;
|
|
109
|
+
/** Run the command `name` (a click on its item): the same path Enter takes. A no-op
|
|
110
|
+
* with no menu open, which is the state a stale click lands in. */
|
|
111
|
+
slashPick(name: string): void;
|
|
114
112
|
/** The current PM selection as a USV `{ from, to }` range (the boundary currency). */
|
|
115
113
|
selectionRange(): {
|
|
116
114
|
from: number;
|
|
@@ -123,9 +121,9 @@ export interface FieldController {
|
|
|
123
121
|
* of {@link FieldController}: they are PM internals, and a consumer's contract is the
|
|
124
122
|
* controller's verbs. The format popover is the one caller and it needs both:
|
|
125
123
|
* `view` to name the leaf's own document, `focusedView` to act on wherever the caret
|
|
126
|
-
* actually is, which inside a table island is a
|
|
124
|
+
* actually is, which inside a table island is a nested cell view (`table-view.ts`).
|
|
127
125
|
* The two being different is exactly what tells the popover to withhold its
|
|
128
|
-
* `anchor` button: an anchor is the
|
|
126
|
+
* `anchor` button: an anchor is the field's coordinate space, and a cell is not in it.
|
|
129
127
|
*/
|
|
130
128
|
export interface LeafViews {
|
|
131
129
|
view: EditorView;
|
|
@@ -144,24 +142,21 @@ export declare function createField(opts: CreateFieldOpts): FieldController;
|
|
|
144
142
|
* leaf-specific plugins (`afterHistory`: the addressed leaf passes its
|
|
145
143
|
* anchor-position plugin; a by-value array element passes none), the
|
|
146
144
|
* markdown-shorthand input rules, then the field keymap over the base keymap, and
|
|
147
|
-
* last the two that answer to a caret or a selection
|
|
145
|
+
* last the two that answer to a caret or a selection beside a block: the gap cursor
|
|
148
146
|
* and {@link pastAtomPlugin}.
|
|
149
147
|
*
|
|
150
148
|
* A `plaintext` field carries no marks (decode strips them, the keymap suppresses
|
|
151
149
|
* Mod-b/i/u), so it also skips the input rules: `inlineSchema` still declares the
|
|
152
|
-
* mark types, so a `**bold**` rule would apply a strong mark
|
|
153
|
-
* delimiters. The inline schema has no block nodes, so those rules are the
|
|
150
|
+
* mark types, so a `**bold**` rule would apply a strong mark and eat the literal
|
|
151
|
+
* delimiters. The inline schema has no block nodes, so those rules are the only
|
|
154
152
|
* ones it would add: skip all.
|
|
155
153
|
*/
|
|
156
154
|
export declare function proseLeafPlugins(schema: Schema, opts: {
|
|
157
155
|
inline: boolean;
|
|
158
156
|
plaintext: boolean;
|
|
159
|
-
/** The slash menu's
|
|
160
|
-
*
|
|
161
|
-
slash?:
|
|
162
|
-
items: () => SlashItem[];
|
|
163
|
-
onState: (state: SlashState | undefined) => void;
|
|
164
|
-
};
|
|
157
|
+
/** The slash menu's report channel; absent mounts no menu and leaves
|
|
158
|
+
* Enter/Escape/the arrows to the links below (`keymap.ts`). */
|
|
159
|
+
slash?: (state: SlashState | undefined) => void;
|
|
165
160
|
noInputRules?: boolean;
|
|
166
161
|
/** Read live, not captured: the ghost can move after mount
|
|
167
162
|
* ({@link FieldController.setPlaceholder}), and a re-hydration rebuilds this
|
package/dist/core/codec/field.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// (b) lower the tr to a `ChangeBundle` (or `overwrite` for a field not yet at
|
|
7
7
|
// content rest, which has nothing to splice),
|
|
8
8
|
// (c) commit via `doc.applyChange(addr, bundle)`,
|
|
9
|
-
// (d) fire `onChange` when the transaction
|
|
9
|
+
// (d) fire `onChange` when the transaction committed, then `onCaretMove` with
|
|
10
10
|
// the new USV caret, which a bare selection move fires on its own.
|
|
11
11
|
// On an `applyChange` throw the optimistic PM state stays and the failure reports
|
|
12
12
|
// through `onError`: never a crash. Caret continuity across own-edits is the PM
|
|
13
|
-
// `StepMap`; an
|
|
13
|
+
// `StepMap`; an external content change re-hydrates through `applyExternal`, gated
|
|
14
14
|
// by `reconcile`.
|
|
15
15
|
import { baseKeymap, toggleMark } from 'prosemirror-commands';
|
|
16
16
|
import { gapCursor } from 'prosemirror-gapcursor';
|
|
@@ -19,6 +19,7 @@ import { keymap } from 'prosemirror-keymap';
|
|
|
19
19
|
import { EditorState, NodeSelection, Plugin, PluginKey, Selection, TextSelection } from 'prosemirror-state';
|
|
20
20
|
import { Decoration, DecorationSet, EditorView } from 'prosemirror-view';
|
|
21
21
|
import { reportError, errorMessage } from '../errors.js';
|
|
22
|
+
import { tableClipboardPlugin } from './clipboard.js';
|
|
22
23
|
import { decode } from './decode.js';
|
|
23
24
|
import { usvToPM, pmToUsv, buildLineIndex } from './positions.js';
|
|
24
25
|
import { lower, pmToContent, contentEdit } from './encode.js';
|
|
@@ -28,7 +29,7 @@ import { inputRulesPlugin } from './inputrules.js';
|
|
|
28
29
|
import { bodyKeymap } from './keymap.js';
|
|
29
30
|
import { blockSchema, inlineSchema } from './schema.js';
|
|
30
31
|
import { DEFAULT_TABLE_STRINGS, tableNodeView } from './table-view.js';
|
|
31
|
-
import {
|
|
32
|
+
import { focusSlashItem, runSlashItem, slashPlugin } from './slash.js';
|
|
32
33
|
const anchorKey = new PluginKey('quill-anchors');
|
|
33
34
|
/** Plugin-held anchor positions (PM coords), mapped through every edit's `StepMap`
|
|
34
35
|
* and mutated by an {@link AnchorEdit} meta (an insert/remove at a selection). */
|
|
@@ -62,8 +63,8 @@ function anchorPlugin(seed) {
|
|
|
62
63
|
*
|
|
63
64
|
* Bound rather than verbatim, because the storage form is not the leaf's business
|
|
64
65
|
* and the verbatim read cannot answer it alone. A field the editor committed rests
|
|
65
|
-
* as `Content`; a field a markdown parse left rests as the authored
|
|
66
|
-
* what that string
|
|
66
|
+
* as `Content`; a field a markdown parse left rests as the authored string, and
|
|
67
|
+
* what that string means is the declared type's to say: `richtext` is markdown,
|
|
67
68
|
* `plaintext` is literal text, and reading one as the other silently eats every
|
|
68
69
|
* `*` the author typed. */
|
|
69
70
|
function readLeaf(reader, addr) {
|
|
@@ -77,9 +78,9 @@ export function emptyContent() {
|
|
|
77
78
|
/** Whether ops may commit here: the stored value is a `Content` object, so
|
|
78
79
|
* `applyChange` splices exactly the content the leaf read and PM is showing.
|
|
79
80
|
*
|
|
80
|
-
* The two other rest forms both take `overwrite` instead. An
|
|
81
|
+
* The two other rest forms both take `overwrite` instead. An unset field (`undefined`;
|
|
81
82
|
* a default-only richtext field before its first edit) has nothing to splice.
|
|
82
|
-
* An
|
|
83
|
+
* An authored string is the trap: `applyChange` reads it as markdown whatever the
|
|
83
84
|
* declared type is, so on a `plaintext` field its pre-image is not the content the
|
|
84
85
|
* leaf read and a delta computed against that content lands at the wrong offsets.
|
|
85
86
|
* Installing over it costs nothing either way, since content-only marks do not
|
|
@@ -119,15 +120,14 @@ export function createField(opts) {
|
|
|
119
120
|
// The ghost's live cell: the placeholder plugin reads it per pass, so moving it
|
|
120
121
|
// is an assignment plus a re-render rather than a rebuilt plugin stack.
|
|
121
122
|
let placeholderText = opts.placeholder;
|
|
122
|
-
// The views nested
|
|
123
|
+
// The views nested inside this leaf: one per table cell (`table-view.ts`). The
|
|
123
124
|
// leaf holds the set because chrome asks the leaf, not the island, which view
|
|
124
125
|
// holds the caret: the format popover raises over a cell exactly as over the
|
|
125
126
|
// body, and only the leaf knows both.
|
|
126
127
|
const nested = new Set();
|
|
127
|
-
// The menu's
|
|
128
|
-
//
|
|
129
|
-
const
|
|
130
|
-
const slash = !inline && opts.onSlash ? { items: menuItems, onState: opts.onSlash } : undefined;
|
|
128
|
+
// The menu's report channel, and the whole of what a leaf holds of it: the
|
|
129
|
+
// vocabulary is the codec's own constant (`slash.ts`), not a wording to derive.
|
|
130
|
+
const slash = inline ? undefined : opts.onSlash;
|
|
131
131
|
const state = buildState(reconciler.last);
|
|
132
132
|
index = buildLineIndex(state.doc);
|
|
133
133
|
view = new EditorView(container, {
|
|
@@ -154,7 +154,7 @@ export function createField(opts) {
|
|
|
154
154
|
const next = view.state.apply(tr);
|
|
155
155
|
view.updateState(next); // (a) optimistic
|
|
156
156
|
index = buildLineIndex(next.doc);
|
|
157
|
-
// Commit a content edit
|
|
157
|
+
// Commit a content edit or an anchor mutation. An anchor insert/remove is
|
|
158
158
|
// zero-width, so `docChanged` is false: the `anchorKey` meta is what
|
|
159
159
|
// routes it through the same commit path (the diff emits the anchor op).
|
|
160
160
|
if (tr.docChanged || tr.getMeta(anchorKey)) {
|
|
@@ -203,7 +203,7 @@ export function createField(opts) {
|
|
|
203
203
|
// Returns whether anything landed: the caller's change signal, which must not
|
|
204
204
|
// be a property of which branch the commit took.
|
|
205
205
|
function commitEdit(oldRt, newDoc) {
|
|
206
|
-
// The projection and its text splice, each computed
|
|
206
|
+
// The projection and its text splice, each computed once per keystroke: the gate
|
|
207
207
|
// below, both overwrite fallbacks, and `lower` all read this one `edit`.
|
|
208
208
|
const edit = contentEdit(oldRt, pmToContent(newDoc));
|
|
209
209
|
try {
|
|
@@ -226,7 +226,7 @@ export function createField(opts) {
|
|
|
226
226
|
return true;
|
|
227
227
|
}
|
|
228
228
|
catch (e) {
|
|
229
|
-
// Bound the damage: an op path the gates missed leaves the store
|
|
229
|
+
// Bound the damage: an op path the gates missed leaves the store stale
|
|
230
230
|
// while PM keeps the edit; and because the reconciler then re-diffs
|
|
231
231
|
// from that stale content, every later edit re-throws and the field
|
|
232
232
|
// silently stops persisting. Install the full projection instead
|
|
@@ -256,7 +256,7 @@ export function createField(opts) {
|
|
|
256
256
|
}
|
|
257
257
|
/** The anchors the plugin currently holds (PM coords); `[]` before the first apply. */
|
|
258
258
|
const heldAnchors = () => anchorKey.getState(view.state) ?? [];
|
|
259
|
-
/** Anchor positions in the
|
|
259
|
+
/** Anchor positions in the current (new) doc, as USV. */
|
|
260
260
|
function readAnchorsUsv(newDoc) {
|
|
261
261
|
return heldAnchors().map((a) => ({ id: a.id, pos: pmToUsv(index, a.pos) }));
|
|
262
262
|
}
|
|
@@ -326,13 +326,13 @@ export function createField(opts) {
|
|
|
326
326
|
})
|
|
327
327
|
.map((a) => a.id);
|
|
328
328
|
},
|
|
329
|
-
slashFocus(
|
|
329
|
+
slashFocus(name) {
|
|
330
330
|
if (slash)
|
|
331
|
-
focusSlashItem(view,
|
|
331
|
+
focusSlashItem(view, name);
|
|
332
332
|
},
|
|
333
|
-
slashPick(
|
|
333
|
+
slashPick(name) {
|
|
334
334
|
if (slash)
|
|
335
|
-
runSlashItem(view,
|
|
335
|
+
runSlashItem(view, name);
|
|
336
336
|
},
|
|
337
337
|
selectionRange() {
|
|
338
338
|
const { from, to } = view.state.selection;
|
|
@@ -365,13 +365,13 @@ export function createField(opts) {
|
|
|
365
365
|
* leaf-specific plugins (`afterHistory`: the addressed leaf passes its
|
|
366
366
|
* anchor-position plugin; a by-value array element passes none), the
|
|
367
367
|
* markdown-shorthand input rules, then the field keymap over the base keymap, and
|
|
368
|
-
* last the two that answer to a caret or a selection
|
|
368
|
+
* last the two that answer to a caret or a selection beside a block: the gap cursor
|
|
369
369
|
* and {@link pastAtomPlugin}.
|
|
370
370
|
*
|
|
371
371
|
* A `plaintext` field carries no marks (decode strips them, the keymap suppresses
|
|
372
372
|
* Mod-b/i/u), so it also skips the input rules: `inlineSchema` still declares the
|
|
373
|
-
* mark types, so a `**bold**` rule would apply a strong mark
|
|
374
|
-
* delimiters. The inline schema has no block nodes, so those rules are the
|
|
373
|
+
* mark types, so a `**bold**` rule would apply a strong mark and eat the literal
|
|
374
|
+
* delimiters. The inline schema has no block nodes, so those rules are the only
|
|
375
375
|
* ones it would add: skip all.
|
|
376
376
|
*/
|
|
377
377
|
export function proseLeafPlugins(schema, opts) {
|
|
@@ -380,21 +380,22 @@ export function proseLeafPlugins(schema, opts) {
|
|
|
380
380
|
list.push(slashPlugin(opts.slash));
|
|
381
381
|
if (!opts.noInputRules && !opts.plaintext)
|
|
382
382
|
list.push(inputRulesPlugin(schema));
|
|
383
|
-
list.push(keymap(editorKeymap(schema, opts.inline, opts.plaintext, opts.slash
|
|
383
|
+
list.push(keymap(editorKeymap(schema, opts.inline, opts.plaintext, !!opts.slash)));
|
|
384
384
|
list.push(keymap(baseKeymap));
|
|
385
|
-
//
|
|
386
|
-
//
|
|
385
|
+
// All three answer to something only a block leaf holds: an inline leaf is one
|
|
386
|
+
// textblock with no island, no gap to put a cursor in, and nowhere for a pasted
|
|
387
|
+
// table to land.
|
|
387
388
|
if (!opts.inline)
|
|
388
|
-
list.push(gapCursor(), pastAtomPlugin());
|
|
389
|
+
list.push(gapCursor(), pastAtomPlugin(), tableClipboardPlugin(schema));
|
|
389
390
|
if (opts.placeholder)
|
|
390
391
|
list.push(placeholderPlugin(opts.placeholder));
|
|
391
392
|
return list;
|
|
392
393
|
}
|
|
393
394
|
/**
|
|
394
|
-
* A printable key over a selected atom writes
|
|
395
|
+
* A printable key over a selected atom writes past it. A selection is the subject
|
|
395
396
|
* of the next command, never a thing armed for
|
|
396
397
|
* replacement: Backspace deletes it, Mod-C copies it, and a character lands beside
|
|
397
|
-
* it. A
|
|
398
|
+
* it. A block island takes a new paragraph after it, an inline one the caret after
|
|
398
399
|
* the image in the same line: one rule, both node types.
|
|
399
400
|
*
|
|
400
401
|
* PM routes text input over a non-`TextSelection` through `handleTextInput` before
|
|
@@ -461,13 +462,13 @@ function placeholderPlugin(read) {
|
|
|
461
462
|
* The field's keymap: history, mark toggles, and the body's structural keys; Enter
|
|
462
463
|
* suppressed inline.
|
|
463
464
|
*
|
|
464
|
-
* Tab forks on the leaf's
|
|
465
|
+
* Tab forks on the leaf's role, not on the caret's position. An inline/plaintext
|
|
465
466
|
* leaf is a form field: Tab stays unbound, so field navigation is open for a shell
|
|
466
467
|
* keymap (VISUAL_EDITOR §Settled and open). A block-schema body is a document: Tab
|
|
467
468
|
* is structural, a chain each nested surface prepends to (`keymap.ts`). One key
|
|
468
469
|
* never means two things within one surface.
|
|
469
470
|
*/
|
|
470
|
-
function editorKeymap(schema, inline, plaintext,
|
|
471
|
+
function editorKeymap(schema, inline, plaintext, slash) {
|
|
471
472
|
const map = {
|
|
472
473
|
'Mod-z': undo,
|
|
473
474
|
'Mod-y': redo,
|
|
@@ -487,7 +488,7 @@ function editorKeymap(schema, inline, plaintext, slashItems) {
|
|
|
487
488
|
}
|
|
488
489
|
else {
|
|
489
490
|
// Each binding falls through to `baseKeymap` when no link claims the key.
|
|
490
|
-
Object.assign(map, bodyKeymap(schema,
|
|
491
|
+
Object.assign(map, bodyKeymap(schema, slash));
|
|
491
492
|
}
|
|
492
493
|
return map;
|
|
493
494
|
}
|
package/dist/core/codec/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// The codec barrel: the bidirectional bridge between one content field (`Content`)
|
|
2
|
-
// and one ProseMirror document (CODEC). Decode (content → PM), lower (PM tr → a
|
|
2
|
+
// and one ProseMirror document (CODEC.md). Decode (content → PM), lower (PM tr → a
|
|
3
3
|
// `ChangeBundle` for `applyChange`), the USV↔PM position map, and the `createField`
|
|
4
4
|
// prose leaf. Consumed by the VisualEditor and the codec test suite.
|
|
5
5
|
// ProseMirror's structural base styles and the gap cursor's, then the retint of the
|
|
6
|
-
// hues they mint. Imported
|
|
6
|
+
// hues they mint. Imported here because `createField` is what mounts a view: a
|
|
7
7
|
// consumer reaching the codec directly gets them with it, and the order is what makes
|
|
8
8
|
// the retint win.
|
|
9
9
|
import 'prosemirror-view/style/prosemirror.css';
|
|
@@ -24,12 +24,9 @@ export { DEFAULT_SLASH_STRINGS } from './slash.js';
|
|
|
24
24
|
export { blockSchema, inlineSchema } from './schema.js';
|
|
25
25
|
// Decode / encode / positions (tests + VisualEditor).
|
|
26
26
|
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// within `codec/`, so none of them is here. An export nothing imports is surface
|
|
31
|
-
// that still has to stay honest; a symbol earns its line when a caller outside
|
|
32
|
-
// this folder wants it.
|
|
27
|
+
// The barrel carries what has an off-barrel caller, and nothing else. A symbol
|
|
28
|
+
// reached only by relative import within `codec/` stays off it: an export nothing
|
|
29
|
+
// imports is surface that still has to stay honest.
|
|
33
30
|
export { decode, renderContent, usvLength } from './decode.js';
|
|
34
31
|
export { pmToContent, contentEdit, lower } from './encode.js';
|
|
35
32
|
export { usvToPM, pmToUsv, buildLineIndex } from './positions.js';
|