@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.
- package/NOTICE +4 -4
- package/README.md +26 -8
- package/THEMING.md +40 -4
- package/dist/core/address.d.ts +35 -64
- package/dist/core/address.js +35 -55
- package/dist/core/bloom.d.ts +4 -10
- package/dist/core/bloom.js +14 -27
- package/dist/core/codec/blocks.d.ts +41 -43
- package/dist/core/codec/blocks.js +97 -130
- package/dist/core/codec/code.d.ts +7 -10
- package/dist/core/codec/code.js +37 -46
- package/dist/core/codec/decode.d.ts +19 -23
- package/dist/core/codec/decode.js +76 -111
- package/dist/core/codec/encode.d.ts +11 -15
- package/dist/core/codec/encode.js +60 -67
- package/dist/core/codec/field.d.ts +62 -95
- package/dist/core/codec/field.js +107 -179
- package/dist/core/codec/islands.d.ts +33 -41
- package/dist/core/codec/islands.js +76 -86
- package/dist/core/codec/lists.d.ts +17 -26
- package/dist/core/codec/lists.js +117 -157
- package/dist/core/codec/marks.d.ts +4 -3
- package/dist/core/codec/marks.js +8 -3
- package/dist/core/codec/prose.css +84 -46
- package/dist/core/codec/schema.d.ts +18 -23
- package/dist/core/codec/schema.js +84 -76
- package/dist/core/codec/slash.d.ts +42 -48
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +4 -3
- package/dist/core/codec/table-copy.js +25 -7
- package/dist/core/codec/table-view.d.ts +12 -12
- package/dist/core/codec/table-view.js +460 -383
- package/dist/core/codec/table.d.ts +94 -61
- package/dist/core/codec/table.js +331 -83
- package/dist/core/codec/tasks.d.ts +6 -0
- package/dist/core/codec/tasks.js +30 -3
- package/dist/core/errors.d.ts +15 -30
- package/dist/core/errors.js +2 -6
- package/dist/core/index.js +7 -12
- package/dist/core/lifecycle.d.ts +7 -16
- package/dist/core/lifecycle.js +10 -21
- package/dist/core/rebind.svelte.d.ts +10 -20
- package/dist/core/rebind.svelte.js +15 -27
- package/dist/core/rungs.d.ts +14 -11
- package/dist/core/rungs.js +17 -19
- package/dist/core/teardown.d.ts +4 -6
- package/dist/core/teardown.js +7 -10
- package/dist/core/theme.css +74 -54
- package/dist/index.js +3 -8
- package/dist/preset/app.css +71 -0
- package/dist/preset/index.css +8 -7
- package/dist/preset/recipes.css +19 -164
- package/dist/preset/scale.css +28 -18
- package/dist/preset/shell.css +157 -0
- package/dist/preview/Preview.svelte +8 -0
- package/dist/preview/bridge.d.ts +18 -22
- package/dist/preview/bridge.js +29 -58
- package/dist/preview/controller.d.ts +21 -29
- package/dist/preview/controller.js +14 -39
- package/dist/preview/geometry.d.ts +13 -34
- package/dist/preview/geometry.js +13 -34
- package/dist/preview/index.js +4 -16
- package/dist/preview/paint.d.ts +3 -3
- package/dist/preview/paint.js +28 -68
- package/dist/preview/strings.d.ts +1 -1
- package/dist/preview/strings.js +2 -14
- package/dist/preview/viewport.js +20 -15
- package/dist/preview/zoom.d.ts +21 -22
- package/dist/preview/zoom.js +53 -30
- package/dist/visual/ArrayField.svelte +77 -16
- package/dist/visual/BooleanField.svelte +28 -15
- package/dist/visual/Card.svelte +66 -51
- package/dist/visual/CardControls.svelte +5 -11
- package/dist/visual/DateField.svelte +20 -4
- package/dist/visual/EnumField.svelte +3 -0
- package/dist/visual/FieldHint.svelte +2 -1
- package/dist/visual/FormatPopover.svelte +82 -15
- package/dist/visual/FormatPopover.svelte.d.ts +4 -0
- package/dist/visual/MatrixAdd.svelte +30 -12
- package/dist/visual/MatrixField.svelte +24 -16
- package/dist/visual/NumberField.svelte +7 -0
- package/dist/visual/ObjectField.svelte +4 -4
- package/dist/visual/ProseField.svelte +0 -5
- package/dist/visual/TipsCard.svelte +11 -10
- package/dist/visual/VariantField.svelte +3 -1
- package/dist/visual/VisualEditorInner.svelte +19 -3
- package/dist/visual/controls.css +7 -2
- package/dist/visual/fonts.d.ts +18 -0
- package/dist/visual/fonts.js +59 -0
- package/dist/visual/icons/nodes.d.ts +0 -5
- package/dist/visual/icons/nodes.js +0 -4
- package/dist/visual/props.d.ts +14 -1
- package/dist/visual/strings.d.ts +7 -0
- package/dist/visual/strings.js +5 -2
- package/dist/visual/structure.d.ts +43 -87
- package/dist/visual/structure.js +42 -83
- package/package.json +2 -2
package/dist/core/codec/field.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
// The prose leaf: `createField` (VISUAL_EDITOR §Surface). One content field, one
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// (d) fire `onChange` when the transaction committed, then `onCaretMove` with
|
|
11
|
-
// the new USV caret, which a bare selection move fires on its own.
|
|
12
|
-
// On an `applyChange` throw the optimistic PM state stays and the failure reports
|
|
13
|
-
// through `onError`: never a crash. Caret continuity across own-edits is the PM
|
|
14
|
-
// `StepMap`; an external content change re-hydrates through `applyExternal`, gated
|
|
15
|
-
// by `reconcile` except where the hold it re-evaluates flips.
|
|
1
|
+
// The prose leaf: `createField` (VISUAL_EDITOR §Surface). One content field, one PM view wired to
|
|
2
|
+
// the WASM edit surface.
|
|
3
|
+
//
|
|
4
|
+
// `dispatchTransaction` applies the transaction optimistically, lowers it to a `ChangeBundle` (or
|
|
5
|
+
// `overwrite` for a field not yet at content rest, which has nothing to splice), commits via
|
|
6
|
+
// `doc.applyChange` (a `text` leaf's whole text through the typed writer), then fires `onChange` if
|
|
7
|
+
// it committed and `onCaretMove`. A throw keeps the optimistic PM state and reports through
|
|
8
|
+
// `onError`. An external change re-hydrates through `applyExternal`, gated by `reconcile` except
|
|
9
|
+
// where the hold it re-evaluates flips.
|
|
16
10
|
import { baseKeymap, toggleMark } from 'prosemirror-commands';
|
|
17
11
|
import { gapCursor } from 'prosemirror-gapcursor';
|
|
18
12
|
import { history, redo, undo } from 'prosemirror-history';
|
|
@@ -70,26 +64,18 @@ function anchorPlugin(seed) {
|
|
|
70
64
|
}
|
|
71
65
|
});
|
|
72
66
|
}
|
|
73
|
-
/**
|
|
74
|
-
* the
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* field
|
|
78
|
-
* first
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
* Bound rather than verbatim, because the storage form is not the leaf's business
|
|
83
|
-
* and the verbatim read cannot answer it alone. A field the editor committed rests
|
|
84
|
-
* as `Content`; a field a markdown parse left rests as the authored string, and
|
|
85
|
-
* what that string means is the declared type's to say: `richtext` is markdown,
|
|
86
|
-
* `text` is literal text, and reading one as the other silently eats every `*` the
|
|
87
|
-
* author typed. */
|
|
67
|
+
/**
|
|
68
|
+
* Read the leaf's `Content` for `addr` through `reader.getContent` (DOCUMENT_MODEL): the body when
|
|
69
|
+
* `addr.field` is absent. Bound by the declared type rather than a verbatim read, since a string a
|
|
70
|
+
* markdown parse left means markdown for `richtext` and literal text for `text`; reading one as the
|
|
71
|
+
* other eats every `*` the author typed. An absent field (a default-only richtext field before its
|
|
72
|
+
* first edit) reads `undefined` and decodes empty; the first edit overwrites it. Only an
|
|
73
|
+
* out-of-range `addr.card` throws, unreachable since a removed card unmounts its keyed leaf.
|
|
74
|
+
*/
|
|
88
75
|
function readLeaf(reader, addr) {
|
|
89
76
|
return reader.getContent(addr) ?? emptyContent();
|
|
90
77
|
}
|
|
91
|
-
/** The canonical empty `Content`: one empty `para` line.
|
|
92
|
-
* leaf (or an array's prose element) seeds from. */
|
|
78
|
+
/** The canonical empty `Content`: one empty `para` line. */
|
|
93
79
|
export function emptyContent() {
|
|
94
80
|
return { text: '', lines: [{ containers: [], kind: 'para' }], marks: [], islands: [] };
|
|
95
81
|
}
|
|
@@ -97,24 +83,21 @@ export function emptyContent() {
|
|
|
97
83
|
export function isEmptyContent(rt) {
|
|
98
84
|
return contentEqual(rt, emptyContent());
|
|
99
85
|
}
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
* string carries no anchors to pay. A body always rests as `Content`. A `text` leaf
|
|
107
|
-
* never asks: it commits through the writer ({@link createField}). */
|
|
86
|
+
/**
|
|
87
|
+
* Whether ops may commit here: the stored value is a `Content` object, so `applyChange` splices
|
|
88
|
+
* exactly what PM shows. The other rest forms take `overwrite`: an unset field has nothing to
|
|
89
|
+
* splice, and an authored string carries no anchors to pay. A body always rests as `Content`; a
|
|
90
|
+
* `text` leaf commits through the writer ({@link createField}).
|
|
91
|
+
*/
|
|
108
92
|
function opsCommittable(doc, addr) {
|
|
109
93
|
const stored = doc.getStored(addr);
|
|
110
94
|
return stored !== null && typeof stored === 'object';
|
|
111
95
|
}
|
|
112
|
-
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
|
|
117
|
-
* the absence, not an empty object. */
|
|
96
|
+
/**
|
|
97
|
+
* The naming attributes for the `contenteditable`, shared with `ProseValue`. `aria-labelledby`
|
|
98
|
+
* supersedes `aria-label`, and `undefined` is returned whole when a leaf takes neither, since
|
|
99
|
+
* ProseMirror reads the absence.
|
|
100
|
+
*/
|
|
118
101
|
export function proseAttributes(opts) {
|
|
119
102
|
const attrs = {};
|
|
120
103
|
if (opts.labelledBy)
|
|
@@ -141,13 +124,10 @@ export function createField(opts) {
|
|
|
141
124
|
const { doc, addr, container } = opts;
|
|
142
125
|
const inline = !!opts.inline;
|
|
143
126
|
const plaintext = !!opts.plaintext;
|
|
144
|
-
// The declared type picks the schema
|
|
145
|
-
// `plaintext` suppresses: no mark types to toggle, to paste in, or to mint a
|
|
146
|
-
// shorthand with (`schema.ts`).
|
|
127
|
+
// The declared type picks the schema; `plaintext` suppresses every mark (`schema.ts`).
|
|
147
128
|
const schema = leafSchema({ plaintext, inline });
|
|
148
129
|
const block = isBlockSchema(schema);
|
|
149
|
-
// Bound once
|
|
150
|
-
// every read below sees the commit before it.
|
|
130
|
+
// Bound once: the reader reads live, so every read sees the commit before it.
|
|
151
131
|
const reader = opts.quill.reader(doc);
|
|
152
132
|
const writer = opts.quill.writer(doc);
|
|
153
133
|
let fallback = opts.fallback;
|
|
@@ -164,13 +144,10 @@ export function createField(opts) {
|
|
|
164
144
|
// The ghost's live cell: the ghost plugin reads it per pass, so moving it is an
|
|
165
145
|
// assignment plus a re-render rather than a rebuilt plugin stack.
|
|
166
146
|
let placeholderText = opts.placeholder;
|
|
167
|
-
// The views nested inside this leaf
|
|
168
|
-
//
|
|
169
|
-
// holds the caret: the format popover raises over a cell exactly as over the
|
|
170
|
-
// body, and only the leaf knows both.
|
|
147
|
+
// The views nested inside this leaf, one per table cell (`table-view.ts`): chrome asks the leaf
|
|
148
|
+
// which view holds the caret.
|
|
171
149
|
const nested = new Set();
|
|
172
|
-
// The menu's report channel
|
|
173
|
-
// vocabulary is the codec's own constant (`slash.ts`), not a wording to derive.
|
|
150
|
+
// The menu's report channel; the vocabulary is the codec's own constant (`slash.ts`).
|
|
174
151
|
const slash = block ? opts.onSlash : undefined;
|
|
175
152
|
// Judged of the value being mounted rather than read off a diagnostic: a routed set
|
|
176
153
|
// can predate the value it arrives with, and `validation::not_plain` stands in for
|
|
@@ -183,13 +160,9 @@ export function createField(opts) {
|
|
|
183
160
|
const seeded = buildState(reconciler.last);
|
|
184
161
|
const state = seeded.state;
|
|
185
162
|
index = seeded.index;
|
|
186
|
-
// The clearance PM's reveal keeps around the caret, in this leaf's own line box
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// target's height (PREVIEW.md §"Follow-the-caret scroll"). Measured off the
|
|
190
|
-
// mounted box rather than restated from the rung, and dropped where the
|
|
191
|
-
// derivation is out of reach (jsdom), a fallback there being the scale written
|
|
192
|
-
// where `check:style` cannot read it.
|
|
163
|
+
// The clearance PM's reveal keeps around the caret, in this leaf's own line box: PM's 5px
|
|
164
|
+
// default leaves the next typed line past the fold. Measured off the mounted box and dropped
|
|
165
|
+
// where the derivation is out of reach (jsdom).
|
|
193
166
|
const lineBox = Number.parseFloat(getComputedStyle(container).lineHeight);
|
|
194
167
|
const clearance = Number.isFinite(lineBox) ? lineBox : undefined;
|
|
195
168
|
// Prepended, so what the caller draws inside `container` (a held leaf's note)
|
|
@@ -211,9 +184,8 @@ export function createField(opts) {
|
|
|
211
184
|
nested.add(cellView);
|
|
212
185
|
return () => nested.delete(cellView);
|
|
213
186
|
},
|
|
214
|
-
// A focus event does not bubble, so the leaf's
|
|
215
|
-
//
|
|
216
|
-
// follow a caret clicked straight into a table.
|
|
187
|
+
// A focus event does not bubble, so the leaf's `focus` handler never fires
|
|
188
|
+
// for a cell.
|
|
217
189
|
onCellFocus: () => opts.onFocus?.(addr),
|
|
218
190
|
clearance
|
|
219
191
|
})
|
|
@@ -230,25 +202,20 @@ export function createField(opts) {
|
|
|
230
202
|
if (!held && (tr.docChanged || tr.getMeta(anchorKey)))
|
|
231
203
|
defaulted = false;
|
|
232
204
|
view.updateState(next); // (a) optimistic
|
|
233
|
-
// One walk per structural change
|
|
234
|
-
//
|
|
235
|
-
// it was built from, so the index over it still holds.
|
|
205
|
+
// One walk per structural change; a selection-only transaction leaves the document, so
|
|
206
|
+
// the index still holds.
|
|
236
207
|
const scan = tr.docChanged ? scanDoc(next.doc) : undefined;
|
|
237
208
|
if (scan)
|
|
238
209
|
index = lineIndexOf(scan);
|
|
239
|
-
// Commit a content edit or an anchor mutation
|
|
240
|
-
//
|
|
241
|
-
// routes it through the same commit path (the diff emits the anchor op).
|
|
210
|
+
// Commit a content edit or an anchor mutation; an anchor edit is zero-width
|
|
211
|
+
// (`docChanged` false), so the `anchorKey` meta routes it.
|
|
242
212
|
if (!held && (tr.docChanged || tr.getMeta(anchorKey))) {
|
|
243
213
|
// (d) the change signal, only for what actually landed.
|
|
244
214
|
const newRt = scan ? scanContent(scan) : pmToContent(next.doc);
|
|
245
215
|
if (commitEdit(oldRt, newRt))
|
|
246
216
|
opts.onChange?.(addr);
|
|
247
217
|
}
|
|
248
|
-
// The caret, for
|
|
249
|
-
// following the caret wants an arrow key, and a host recompiling wants
|
|
250
|
-
// `onChange`. Reported after the commit, so the edit has landed by the
|
|
251
|
-
// time the caret names where it is.
|
|
218
|
+
// The caret, for structural and selection-only changes, reported after the commit.
|
|
252
219
|
opts.onCaretMove?.(addr, pmToUsv(index, next.selection.head));
|
|
253
220
|
},
|
|
254
221
|
handleDOMEvents: {
|
|
@@ -258,8 +225,10 @@ export function createField(opts) {
|
|
|
258
225
|
}
|
|
259
226
|
}
|
|
260
227
|
});
|
|
261
|
-
/**
|
|
262
|
-
*
|
|
228
|
+
/**
|
|
229
|
+
* The state, and the index over the document it was built from (`buildLineIndex` is a whole
|
|
230
|
+
* walk).
|
|
231
|
+
*/
|
|
263
232
|
function buildState(rt) {
|
|
264
233
|
if (held) {
|
|
265
234
|
const pmDoc = decode(rt, blockSchema);
|
|
@@ -293,25 +262,22 @@ export function createField(opts) {
|
|
|
293
262
|
});
|
|
294
263
|
return { state: built, index: seedIndex };
|
|
295
264
|
}
|
|
296
|
-
// (b)+(c): lower the edit to ops and commit, or `overwrite` a field not yet at
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
// be a property of which branch the commit took.
|
|
265
|
+
// (b)+(c): lower the edit to ops and commit, or `overwrite` a field not yet at content rest.
|
|
266
|
+
// Keeps the optimistic PM on throw. Returns whether anything landed, independent of which
|
|
267
|
+
// branch ran.
|
|
300
268
|
function commitEdit(oldRt, newRt) {
|
|
301
269
|
if (opts.emptyUnsets && heldAnchors().length === 0 && isEmptyContent(newRt))
|
|
302
270
|
return commitUnset();
|
|
303
271
|
if (plaintext)
|
|
304
272
|
return commitPlain(newRt.text);
|
|
305
|
-
// The text splice, computed once per keystroke
|
|
306
|
-
// fallbacks
|
|
307
|
-
// caller's, off the same walk that rebuilt the index.
|
|
273
|
+
// The text splice, computed once per keystroke and shared by the gate, both overwrite
|
|
274
|
+
// fallbacks and `lower`.
|
|
308
275
|
const edit = contentEdit(oldRt, newRt);
|
|
309
276
|
try {
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
// places a slot the `delta` may not carry.
|
|
277
|
+
// Only a field not yet at content rest overwrites by choice: `applyChange` throws on an
|
|
278
|
+
// absent declared field, and neither it nor an authored string has anchors to lose
|
|
279
|
+
// (`opsCommittable`). Every structural edit lowers; the island channel places a slot
|
|
280
|
+
// the `delta` may not carry.
|
|
315
281
|
if (!opsCommittable(doc, addr)) {
|
|
316
282
|
doc.overwrite(addr, edit.newRt); // brings the field to content rest
|
|
317
283
|
}
|
|
@@ -324,10 +290,8 @@ export function createField(opts) {
|
|
|
324
290
|
return true;
|
|
325
291
|
}
|
|
326
292
|
catch (e) {
|
|
327
|
-
// Bound the damage: an op path the gates missed
|
|
328
|
-
//
|
|
329
|
-
// from that stale content, every later edit re-throws and the field
|
|
330
|
-
// silently stops persisting. Install the full projection instead
|
|
293
|
+
// Bound the damage: after an op path the gates missed, the reconciler re-diffs from
|
|
294
|
+
// stale content and every later edit would re-throw. Install the full projection
|
|
331
295
|
// (correct store, pays this field's anchors).
|
|
332
296
|
reportError(opts.onError, {
|
|
333
297
|
code: 'commit-fallback',
|
|
@@ -353,14 +317,10 @@ export function createField(opts) {
|
|
|
353
317
|
}
|
|
354
318
|
}
|
|
355
319
|
/**
|
|
356
|
-
* A `text` leaf's commit: its whole text through the typed writer,
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* reads back changed. The leaf carries no anchors, so the op path has none to keep.
|
|
361
|
-
*
|
|
362
|
-
* A refusal lands nothing and has no fallback, the other rest form being the
|
|
363
|
-
* corruption; the next edit writes the whole text again.
|
|
320
|
+
* A `text` leaf's commit: its whole text through the typed writer, resting the field as that
|
|
321
|
+
* literal string (CODEC §Inline mode). The content-object rest form would emit markdown
|
|
322
|
+
* (escaped `*`, backslash breaks) and read back changed. A refusal lands nothing and has no
|
|
323
|
+
* fallback; the next edit writes the whole text again.
|
|
364
324
|
*/
|
|
365
325
|
function commitPlain(text) {
|
|
366
326
|
try {
|
|
@@ -409,17 +369,12 @@ export function createField(opts) {
|
|
|
409
369
|
el: container,
|
|
410
370
|
setCaret(pos) {
|
|
411
371
|
const pm = usvToPM(index, pos);
|
|
412
|
-
// `Selection.near
|
|
413
|
-
//
|
|
414
|
-
// nested textblock), where a raw TextSelection is invalid.
|
|
372
|
+
// `Selection.near`: the mapped position can be non-inline, where a raw TextSelection is
|
|
373
|
+
// invalid.
|
|
415
374
|
const sel = Selection.near(view.state.doc.resolve(pm));
|
|
416
|
-
// Focus first, then the caret
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// Flagged, because PM focuses with `preventScroll` and a landing is a caret
|
|
421
|
-
// move like any other: unflagged, the caret an arrow key would have revealed
|
|
422
|
-
// is left off screen by the click that placed it.
|
|
375
|
+
// Focus first, then the caret, the order a click arrives in: the other way round the
|
|
376
|
+
// arrival ends the follow this call just started. Flagged, since PM focuses with
|
|
377
|
+
// `preventScroll` and an unflagged landing leaves the caret off screen.
|
|
423
378
|
view.focus();
|
|
424
379
|
view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
|
|
425
380
|
},
|
|
@@ -463,9 +418,8 @@ export function createField(opts) {
|
|
|
463
418
|
},
|
|
464
419
|
focus() {
|
|
465
420
|
view.focus();
|
|
466
|
-
// The placement rung's reveal: no caret to flag a transaction with, so the
|
|
467
|
-
//
|
|
468
|
-
// lane's own focus takes, for the landing that means the same thing there.
|
|
421
|
+
// The placement rung's reveal: no caret to flag a transaction with, so the trip is the
|
|
422
|
+
// element's, `nearest`, no clearance.
|
|
469
423
|
view.dom.scrollIntoView({ block: 'nearest' });
|
|
470
424
|
},
|
|
471
425
|
getContent() {
|
|
@@ -477,9 +431,8 @@ export function createField(opts) {
|
|
|
477
431
|
return;
|
|
478
432
|
if (heldAnchors().some((a) => a.id === id))
|
|
479
433
|
return; // ids are unique + invariant (0.97 policy)
|
|
480
|
-
// An anchor commits through `applyChange`, so the field is brought to content
|
|
481
|
-
//
|
|
482
|
-
// the just-added anchor.
|
|
434
|
+
// An anchor commits through `applyChange`, so the field is brought to content rest
|
|
435
|
+
// first or the overwrite branch would drop it.
|
|
483
436
|
if (!opsCommittable(doc, addr))
|
|
484
437
|
doc.overwrite(addr, reconciler.last);
|
|
485
438
|
const pm = usvToPM(index, pos);
|
|
@@ -519,8 +472,7 @@ export function createField(opts) {
|
|
|
519
472
|
view.destroy();
|
|
520
473
|
}
|
|
521
474
|
};
|
|
522
|
-
// The underlying views, exposed
|
|
523
|
-
// VisualEditor composes views, and tests drive edits through them.
|
|
475
|
+
// The underlying views, exposed for VisualEditor and tests.
|
|
524
476
|
const handles = controller;
|
|
525
477
|
handles.view = view;
|
|
526
478
|
handles.nestedViews = () => [...nested];
|
|
@@ -535,24 +487,17 @@ export function createField(opts) {
|
|
|
535
487
|
return controller;
|
|
536
488
|
}
|
|
537
489
|
/**
|
|
538
|
-
* The prose-leaf plugin stack (VISUAL_EDITOR §Surface)
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
* that links a selection (`link-paste.ts`), and last the ones that answer to a block
|
|
547
|
-
* leaf's own shapes: the gap cursor, {@link pastAtomPlugin}, the island paste pass
|
|
548
|
-
* (`islands.ts`), the table paste (`table-paste.ts`), the markdown paste
|
|
549
|
-
* (`markdown-paste.ts`) and the task item's checkbox (`tasks.ts`). A one-line leaf's
|
|
550
|
-
* paste joins its lines (`line-paste.ts`).
|
|
490
|
+
* The prose-leaf plugin stack (VISUAL_EDITOR §Surface), shared by {@link createField} and
|
|
491
|
+
* `ProseValue` so the two never fork the plugin ordering. History first with the step-splitting
|
|
492
|
+
* plugin (`undo.ts`), then `afterHistory` plugins (the addressed leaf's anchor plugin), {@link
|
|
493
|
+
* linebreakPlugin}, the markdown-shorthand input rules and their Backspace, the field keymap over
|
|
494
|
+
* the base keymap, the URL paste that links a selection (`link-paste.ts`), and last the block
|
|
495
|
+
* leaf's own shapes: the gap cursor, {@link pastAtomPlugin}, the island paste (`islands.ts`), table
|
|
496
|
+
* paste (`table-paste.ts`), markdown paste (`markdown-paste.ts`) and the task checkbox
|
|
497
|
+
* (`tasks.ts`). A one-line leaf's paste joins its lines (`line-paste.ts`).
|
|
551
498
|
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
* toggles bind no key and a pasted URL stays text, so a `**bold**` keeps the delimiters
|
|
555
|
-
* its author typed without a second rule saying so.
|
|
499
|
+
* Mark-shaped plugins read the schema rather than a flag: over a mark-free schema the shorthand
|
|
500
|
+
* rules build nothing, the toggles bind no key and a pasted URL stays text.
|
|
556
501
|
*/
|
|
557
502
|
export function proseLeafPlugins(schema, opts) {
|
|
558
503
|
const list = [
|
|
@@ -570,8 +515,7 @@ export function proseLeafPlugins(schema, opts) {
|
|
|
570
515
|
list.push(keymap(baseKeymap));
|
|
571
516
|
if (schema.marks.link)
|
|
572
517
|
list.push(linkPastePlugin(schema.marks.link));
|
|
573
|
-
// Each answers to something only the block schema holds
|
|
574
|
-
// textblocks alone, with no island and no gap to put a cursor in.
|
|
518
|
+
// Each answers to something only the block schema holds.
|
|
575
519
|
if (isBlockSchema(schema))
|
|
576
520
|
list.push(gapCursor(), pastAtomPlugin(), islandPastePlugin(), tablePastePlugin(), tableCopyPlugin(), markdownPastePlugin(), wordPastePlugin(), listPastePlugin(), taskPlugin(), nestingPlugin());
|
|
577
521
|
if (schema === plainSchema)
|
|
@@ -583,10 +527,8 @@ export function proseLeafPlugins(schema, opts) {
|
|
|
583
527
|
return list;
|
|
584
528
|
}
|
|
585
529
|
/**
|
|
586
|
-
* A plain leaf's clipboard text is literal, one line per `\n` both ways
|
|
587
|
-
*
|
|
588
|
-
* blank line the value holds as content, and write a copy's blocks joined by a blank
|
|
589
|
-
* line and its breaks as nothing.
|
|
530
|
+
* A plain leaf's clipboard text is literal, one line per `\n` both ways; upstream's defaults split
|
|
531
|
+
* pasted text on any run of newlines and write a copy's blocks joined by a blank line.
|
|
590
532
|
*/
|
|
591
533
|
function plainClipboardPlugin() {
|
|
592
534
|
const { paragraph } = plainSchema.nodes;
|
|
@@ -600,18 +542,11 @@ function plainClipboardPlugin() {
|
|
|
600
542
|
});
|
|
601
543
|
}
|
|
602
544
|
/**
|
|
603
|
-
* A printable key over a selected atom writes past it
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
* types. Only a paragraph is reused, since an empty heading or fence would set the
|
|
609
|
-
* text in a shape the key never asked for.
|
|
610
|
-
*
|
|
611
|
-
* PM routes text input over a non-`TextSelection` through `handleTextInput` before
|
|
612
|
-
* falling back to replacing the selection, so this one prop is the whole of it.
|
|
613
|
-
* Paste keeps replacing: a paste is deliberate, and it is the gesture that swaps one
|
|
614
|
-
* island for another.
|
|
545
|
+
* A printable key over a selected atom writes past it: a selection is the subject of the next
|
|
546
|
+
* command, not armed for replacement. A block island takes the empty paragraph after it, or a new
|
|
547
|
+
* one; an inline one the caret after the image. Only a paragraph is reused. PM routes text input
|
|
548
|
+
* over a non-`TextSelection` through `handleTextInput`, so this one prop is the whole of it. Paste
|
|
549
|
+
* keeps replacing, as the gesture that swaps one island for another.
|
|
615
550
|
*/
|
|
616
551
|
function pastAtomPlugin() {
|
|
617
552
|
return new Plugin({
|
|
@@ -641,17 +576,11 @@ function pastAtomPlugin() {
|
|
|
641
576
|
});
|
|
642
577
|
}
|
|
643
578
|
/**
|
|
644
|
-
* The empty leaf's ghost
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
* Under `placeholderUntilEdit` it is stamped only until the state's first edit, after
|
|
650
|
-
* which the leaf has been answered. An inline leaf's ghost keeps to the one line the
|
|
651
|
-
* leaf is (`qm-prose-placeholder-line`).
|
|
652
|
-
*
|
|
653
|
-
* The text is read per decoration pass rather than closed over, so moving the ghost is
|
|
654
|
-
* a re-render and never a document edit.
|
|
579
|
+
* The empty leaf's ghost: a node decoration stamps the sole empty textblock with a class and the
|
|
580
|
+
* ghost text, which `prose.css` draws from the attribute, so the text never enters the document or
|
|
581
|
+
* a `pmToContent` export. Under `placeholderUntilEdit` it is stamped only until the state's first
|
|
582
|
+
* edit. An inline leaf's ghost keeps to one line (`qm-prose-placeholder-line`). The text is read
|
|
583
|
+
* per decoration pass, so moving the ghost is a re-render.
|
|
655
584
|
*/
|
|
656
585
|
function ghostPlugin(opts) {
|
|
657
586
|
const key = new PluginKey('qm-ghost');
|
|
@@ -684,15 +613,12 @@ function ghostPlugin(opts) {
|
|
|
684
613
|
});
|
|
685
614
|
}
|
|
686
615
|
/**
|
|
687
|
-
* The field's keymap: history, mark toggles
|
|
688
|
-
*
|
|
689
|
-
* `text` leaf leaves Mod-b/i/u to the browser.
|
|
616
|
+
* The field's keymap: history, mark toggles (bound per mark type the schema declares), and the
|
|
617
|
+
* body's structural keys; Enter suppressed inline.
|
|
690
618
|
*
|
|
691
|
-
* Tab forks on the leaf's role
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
* is structural, a chain each nested surface prepends to (`keymap.ts`). One key
|
|
695
|
-
* never means two things within one surface.
|
|
619
|
+
* Tab forks on the leaf's role: a one-line or `text` leaf is a form field, Tab unbound so field
|
|
620
|
+
* navigation is open for a shell keymap (VISUAL_EDITOR §Settled and open); a block-schema body
|
|
621
|
+
* takes Tab structurally, a chain each nested surface prepends to (`keymap.ts`).
|
|
696
622
|
*/
|
|
697
623
|
function editorKeymap(schema, inline, slash) {
|
|
698
624
|
const map = {
|
|
@@ -706,11 +632,13 @@ function editorKeymap(schema, inline, slash) {
|
|
|
706
632
|
map['Mod-i'] = toggleMark(schema.marks.em);
|
|
707
633
|
if (schema.marks.underline)
|
|
708
634
|
map['Mod-u'] = toggleMark(schema.marks.underline);
|
|
635
|
+
if (schema.marks.strike)
|
|
636
|
+
map['Mod-Shift-x'] = toggleMark(schema.marks.strike);
|
|
637
|
+
if (schema.marks.code)
|
|
638
|
+
map['Mod-e'] = toggleMark(schema.marks.code);
|
|
709
639
|
if (inline) {
|
|
710
|
-
// One textblock only: swallow Enter
|
|
711
|
-
//
|
|
712
|
-
// declares no break node, and a declined key is the browser's, whose native
|
|
713
|
-
// `<br>` is the one thing that could put a second line in a field that holds one.
|
|
640
|
+
// One textblock only: swallow Enter, and Shift-Enter with it (the schema declares no break
|
|
641
|
+
// node; a declined key is the browser's native `<br>`).
|
|
714
642
|
map['Enter'] = () => true;
|
|
715
643
|
map['Shift-Enter'] = () => true;
|
|
716
644
|
}
|
|
@@ -7,64 +7,56 @@ export declare const ISLAND_SLOT = "\uFFFC";
|
|
|
7
7
|
export declare function isBlockIsland(type: ContentIsland['type']): boolean;
|
|
8
8
|
/** Whether a leaf edits an island of `type`; one holding any other is held (`fitsLeaf`). */
|
|
9
9
|
export declare function isEditedIsland(type: ContentIsland['type']): boolean;
|
|
10
|
-
/** Block island node (a table): a `U+FFFC` slot alone on its `para` line. Atom
|
|
10
|
+
/** Block island node (a table): a `U+FFFC` slot alone on its `para` line. Atom. */
|
|
11
11
|
export declare const islandBlockSpec: NodeSpec;
|
|
12
|
-
/** Inline island node (an image, a footnote, an equation): a `U+FFFC` slot inside a
|
|
13
|
-
* `para` line. */
|
|
12
|
+
/** Inline island node (an image, a footnote, an equation): a `U+FFFC` slot inside a `para` line. */
|
|
14
13
|
export declare const islandInlineSpec: NodeSpec;
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
14
|
+
/**
|
|
15
|
+
* A PM island node's attributes: the content entry, with `islandType` because `type` on a PM node
|
|
16
|
+
* is the node type; its set comes off the entry (DOCUMENT_MODEL §Stability seams).
|
|
17
|
+
*/
|
|
19
18
|
export interface IslandNodeAttrs {
|
|
20
19
|
id: string;
|
|
21
20
|
islandType: ContentIsland['type'];
|
|
22
21
|
props: unknown;
|
|
23
22
|
}
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
/**
|
|
24
|
+
* Build a content island entry from a PM island node's attrs. The cast pairs a closed `type` with a
|
|
25
|
+
* `props` that crossed the DOM as opaque JSON. The store is lenient about a table's shape, so a
|
|
26
|
+
* payload the arm does not hold is stored, and `undefined` at its reader draws the placeholder
|
|
27
|
+
* (§`tablePropsOfNode`).
|
|
28
|
+
*/
|
|
29
29
|
export declare function islandEntryFromNode(attrs: IslandNodeAttrs): ContentIsland;
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
30
|
+
/**
|
|
31
|
+
* A node's `TableProps`, or `undefined` for any other island and for a payload of the wrong shape:
|
|
32
|
+
* the boundary's guard, so a `props` that crossed the DOM is read once here. Every caller draws the
|
|
33
|
+
* placeholder for `undefined` (`table-view.ts` §`render`).
|
|
34
|
+
*/
|
|
35
35
|
export declare function tablePropsOfNode(node: PMNode): TableProps | undefined;
|
|
36
36
|
/**
|
|
37
|
-
* A minter over one document: the positional `isl-{n}` sequence continued past the
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* counter over the projection in hand — never a UUID, never a clock reading, and never a
|
|
42
|
-
* re-numbering of the ids already there, which are identities.
|
|
37
|
+
* A minter over one document: the positional `isl-{n}` sequence continued past the highest id the
|
|
38
|
+
* field holds, so a caller placing several islands keeps one minter. A minted id is part of the
|
|
39
|
+
* document's canonical bytes (CODEC §Islands): a counter over the projection in hand, never a UUID
|
|
40
|
+
* or clock reading, and never a re-numbering of existing ids.
|
|
43
41
|
*
|
|
44
|
-
* Reads the PM doc rather than
|
|
45
|
-
*
|
|
46
|
-
* a stale content could collide with one the same transaction places.
|
|
42
|
+
* Reads the PM doc rather than stored content because that is what the caller holds and the commit
|
|
43
|
+
* will project; a stale content could collide with an id the same transaction places.
|
|
47
44
|
*/
|
|
48
45
|
export declare function islandMinter(doc: PMNode): () => string;
|
|
49
|
-
/** The next island id for a field: one draw from {@link islandMinter}
|
|
50
|
-
* command placing exactly one island wants. */
|
|
46
|
+
/** The next island id for a field: one draw from {@link islandMinter}. */
|
|
51
47
|
export declare function mintIslandId(doc: PMNode): string;
|
|
52
48
|
/**
|
|
53
|
-
* The paste's island pass: an id the field already holds is re-minted on the way in, and
|
|
54
|
-
*
|
|
55
|
-
* its first.
|
|
49
|
+
* The paste's island pass: an id the field already holds is re-minted on the way in, and an island
|
|
50
|
+
* arriving with none (a table read off foreign markup, `table-paste.ts`) takes its first.
|
|
56
51
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* collides with nothing and keeps the identity it had.
|
|
52
|
+
* Ids are unique across the field, so two islands wearing one is a projection the store has no
|
|
53
|
+
* shape for. That arrives by a copy or a paste from a second field; a cut pasted back collides with
|
|
54
|
+
* nothing and keeps its identity.
|
|
61
55
|
*
|
|
62
|
-
* The test is the document as it stands,
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* duplicate would cost the whole field.
|
|
56
|
+
* The test is the document as it stands, so a drag moving a selection over an island sees the
|
|
57
|
+
* original still there and mints a fresh id for the arrival, which costs nothing next to a
|
|
58
|
+
* duplicate.
|
|
66
59
|
*
|
|
67
|
-
* A plugin prop rather than a view option:
|
|
68
|
-
* which nodes carry an identity is the codec's to know.
|
|
60
|
+
* A plugin prop rather than a view option: which nodes carry an identity is the codec's to know.
|
|
69
61
|
*/
|
|
70
62
|
export declare function islandPastePlugin(): Plugin;
|