@quillmark/svelte 0.27.0 → 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 +10 -8
- 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 +3 -1
- package/dist/preset/index.css +6 -8
- package/dist/preset/recipes.css +12 -15
- package/dist/preset/scale.css +18 -17
- package/dist/preset/shell.css +10 -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/zoom.d.ts +18 -23
- package/dist/preview/zoom.js +28 -27
- package/dist/visual/ArrayField.svelte +77 -16
- package/dist/visual/BooleanField.svelte +27 -14
- package/dist/visual/Card.svelte +61 -48
- 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/code.js
CHANGED
|
@@ -2,9 +2,11 @@ import { Selection, TextSelection } from 'prosemirror-state';
|
|
|
2
2
|
import { newlineInCode } from 'prosemirror-commands';
|
|
3
3
|
/** One indent level. */
|
|
4
4
|
const UNIT = ' ';
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
* range, where the structural links keep
|
|
5
|
+
/**
|
|
6
|
+
* The enclosing code block's content start and text, or `null` when the selection is not wholly
|
|
7
|
+
* inside one: a selection that escapes is a document-level range, where the structural links keep
|
|
8
|
+
* their meaning.
|
|
9
|
+
*/
|
|
8
10
|
function codeBlockAt(state) {
|
|
9
11
|
const { $from, $to } = state.selection;
|
|
10
12
|
if (!$from.parent.type.spec.code)
|
|
@@ -14,11 +16,8 @@ function codeBlockAt(state) {
|
|
|
14
16
|
return { start: $from.start(), text: $from.parent.textContent };
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
|
-
* Start offsets of the lines the range `[f, t)` covers
|
|
18
|
-
*
|
|
19
|
-
* A line whose start is `t` is excluded on a non-empty range: a selection ending at
|
|
20
|
-
* a line start does not reach into that line, the editor convention (else selecting
|
|
21
|
-
* whole lines by dragging to the next line's start indents one line too many).
|
|
19
|
+
* Start offsets of the lines the range `[f, t)` covers. A line whose start is `t` is excluded on a
|
|
20
|
+
* non-empty range, so dragging to the next line's start does not indent one line too many.
|
|
22
21
|
*/
|
|
23
22
|
function coveredLineStarts(text, f, t) {
|
|
24
23
|
const last = t > f ? t - 1 : t;
|
|
@@ -31,9 +30,10 @@ function coveredLineStarts(text, f, t) {
|
|
|
31
30
|
}
|
|
32
31
|
return starts;
|
|
33
32
|
}
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
/**
|
|
34
|
+
* Width of the indent one outdent removes at line offset `s`: a leading tab, else up to one unit of
|
|
35
|
+
* leading spaces; `0` if none.
|
|
36
|
+
*/
|
|
37
37
|
function outdentWidth(text, s) {
|
|
38
38
|
if (text[s] === '\t')
|
|
39
39
|
return 1;
|
|
@@ -43,9 +43,8 @@ function outdentWidth(text, s) {
|
|
|
43
43
|
return n;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Tab in a code block: insert one indent unit, or indent every covered line when the
|
|
47
|
-
*
|
|
48
|
-
* types, the way any other key does.
|
|
46
|
+
* Tab in a code block: insert one indent unit, or indent every covered line when the selection
|
|
47
|
+
* spans a newline. A single-line selection is replaced by the unit, as any key types.
|
|
49
48
|
*/
|
|
50
49
|
const indentInCode = (state, dispatch) => {
|
|
51
50
|
const block = codeBlockAt(state);
|
|
@@ -73,11 +72,9 @@ const indentInCode = (state, dispatch) => {
|
|
|
73
72
|
return true;
|
|
74
73
|
};
|
|
75
74
|
/**
|
|
76
|
-
* Shift-Tab in a code block: remove one indent level from every covered line.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* list link: inside a `list_item > code_block` it lifts the item out
|
|
80
|
-
* (`tests/codec/code-keys.test.ts`), and outside a list the body holds it (`keymap.ts`).
|
|
75
|
+
* Shift-Tab in a code block: remove one indent level from every covered line. Declines when none
|
|
76
|
+
* carries an indent, so the key falls through to the list link (lifting a `list_item > code_block`
|
|
77
|
+
* out of the item; `tests/codec/code-keys.test.ts`) or the body (`keymap.ts`).
|
|
81
78
|
*/
|
|
82
79
|
const outdentInCode = (state, dispatch) => {
|
|
83
80
|
const block = codeBlockAt(state);
|
|
@@ -99,10 +96,9 @@ const outdentInCode = (state, dispatch) => {
|
|
|
99
96
|
return true;
|
|
100
97
|
};
|
|
101
98
|
/**
|
|
102
|
-
* The textblock the caret's
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* joining anything, and so changes nothing here.
|
|
99
|
+
* The textblock the caret's block stands directly against on `side`: its sibling under one parent,
|
|
100
|
+
* the only neighbour a press joins text with. `null` at a container's edge, where the base keymap
|
|
101
|
+
* moves the whole block instead.
|
|
106
102
|
*/
|
|
107
103
|
function blockAgainst(state, side) {
|
|
108
104
|
const { $from, empty } = state.selection;
|
|
@@ -117,18 +113,15 @@ function blockAgainst(state, side) {
|
|
|
117
113
|
return { node, pos: side < 0 ? $edge.pos - node.nodeSize : $edge.pos };
|
|
118
114
|
}
|
|
119
115
|
/**
|
|
120
|
-
* Backspace or Delete at a fence's edge, from either side, edits only the block the caret
|
|
121
|
-
* is in.
|
|
116
|
+
* Backspace or Delete at a fence's edge, from either side, edits only the block the caret is in.
|
|
122
117
|
*
|
|
123
|
-
* Inside the fence, a press at its head or
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* (`schema.ts`). The press after that is an ordinary join between two prose blocks.
|
|
118
|
+
* Inside the fence, a press at its head or end retypes it to a paragraph and joins nothing
|
|
119
|
+
* (`toCodeBlock`'s inverse, `blocks.ts`); `linebreakReplacement` carries the newlines across as
|
|
120
|
+
* breaks (`schema.ts`). The press after is an ordinary prose join.
|
|
127
121
|
*
|
|
128
|
-
* From the prose block against it, the caret steps into the fence
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* line under a fence goes with the step, its Backspace having nothing of its own to edit.
|
|
122
|
+
* From the prose block against it, the caret steps into the fence at the end it faces and the fence
|
|
123
|
+
* is left as it is: a join would retype one side's text as the other's kind. An empty line under a
|
|
124
|
+
* fence goes with the step.
|
|
132
125
|
*
|
|
133
126
|
* Declines where both sides are code and where neither is: those joins retype nothing.
|
|
134
127
|
*/
|
|
@@ -155,9 +148,10 @@ function atProseEdge(side) {
|
|
|
155
148
|
return true;
|
|
156
149
|
};
|
|
157
150
|
}
|
|
158
|
-
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
151
|
+
/**
|
|
152
|
+
* ↓ on the last line of a fence nothing follows → a paragraph after it, the caret there: a gap
|
|
153
|
+
* cursor will not sit beside a fence.
|
|
154
|
+
*/
|
|
161
155
|
const belowFinalFence = (state, dispatch) => {
|
|
162
156
|
const paragraph = state.schema.nodes.paragraph;
|
|
163
157
|
const { $from, empty } = state.selection;
|
|
@@ -177,17 +171,14 @@ const belowFinalFence = (state, dispatch) => {
|
|
|
177
171
|
return true;
|
|
178
172
|
};
|
|
179
173
|
/**
|
|
180
|
-
* The `code_block` link of the body's key chains: `{}` for the one-line and plain
|
|
181
|
-
*
|
|
174
|
+
* The `code_block` link of the body's key chains: `{}` for the one-line and plain schemas, which
|
|
175
|
+
* declare no code node.
|
|
182
176
|
*
|
|
183
|
-
* `Enter` is upstream's `newlineInCode`,
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* Every command here declines away from a code block — inside one for the keys about
|
|
189
|
-
* its text and its last line, against one for the two about its edge — so the list
|
|
190
|
-
* links keep the keys elsewhere.
|
|
177
|
+
* `Enter` is upstream's `newlineInCode`, load-bearing: inside a `list_item > code_block` the list
|
|
178
|
+
* link's `splitListItem` would otherwise split the item in two. `Shift-Enter` is the same command:
|
|
179
|
+
* the line break the key means elsewhere (`breaks.ts`) is, inside a fence, the newline Enter takes.
|
|
180
|
+
* Every command here declines away from a code block (inside it for the text and last-line keys,
|
|
181
|
+
* against it for the two edge keys), leaving the list links the keys elsewhere.
|
|
191
182
|
*/
|
|
192
183
|
export function codeKeymap(schema) {
|
|
193
184
|
if (!schema.nodes.code_block)
|
|
@@ -5,35 +5,31 @@ export declare function codePoints(s: string): string[];
|
|
|
5
5
|
/** USV length of `s` (code points, not UTF-16 units). */
|
|
6
6
|
export declare function usvLength(s: string): number;
|
|
7
7
|
/**
|
|
8
|
-
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* mark vocabulary a leaf edits it in (the tips card). A second renderer over the
|
|
12
|
-
* same content would drift from `decode`; this cannot.
|
|
13
|
-
*
|
|
14
|
-
* `DOMSerializer.fromSchema` memoizes on the schema, so repeat calls build no
|
|
15
|
-
* serializer.
|
|
8
|
+
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`, for chrome that
|
|
9
|
+
* must show content in the mark vocabulary a leaf edits it in (the tips card) without a second
|
|
10
|
+
* renderer to drift from `decode`. `DOMSerializer.fromSchema` memoizes on the schema.
|
|
16
11
|
*/
|
|
17
12
|
export declare function renderContent(rt: Content, schema: Schema): Node;
|
|
18
13
|
/** Decode a `Content` to a PM document under `schema`. */
|
|
19
14
|
export declare function decode(rt: Content, schema: Schema): PMNode;
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Whether `rt` decodes under an inline schema with nothing but whitespace dropped: upstream's
|
|
17
|
+
* one-line rule, or one plain line and the empty one a trailing `\n` opens (the one a YAML `|`
|
|
18
|
+
* scalar keeps in a `text` value). Marks are the schema's business.
|
|
19
|
+
*/
|
|
25
20
|
export declare function fitsInline(rt: Content): boolean;
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
21
|
+
/**
|
|
22
|
+
* Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the next commit
|
|
23
|
+
* heals (CODEC §Inline mode): upstream's `isPlain` with marks set aside.
|
|
24
|
+
*/
|
|
29
25
|
export declare function fitsPlain(rt: Content): boolean;
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Whether a leaf so declared edits `rt` losing nothing: {@link fitsInline} narrowed and {@link
|
|
28
|
+
* fitsPlain} plain, past which the decode drops what the first commit would store. On the block
|
|
29
|
+
* schema, anything but a construct read and not edited (`isEditedIsland`) and an island where the
|
|
30
|
+
* leaf mounts no island view (`islands`), which draws as a placeholder atom a keystroke deletes
|
|
31
|
+
* unseen.
|
|
32
|
+
*/
|
|
37
33
|
export declare function fitsLeaf(rt: Content, leaf: {
|
|
38
34
|
inline: boolean;
|
|
39
35
|
plaintext: boolean;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
// Decode: content → PM, a pure function (CODEC §Decode). Fold the flat lines into
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// island's slot stands alone on its line, inline otherwise). Anchors are not
|
|
8
|
-
// applied here; they are decorations (field.ts). Positions throughout are USV; `Array.from` iterates by
|
|
9
|
-
// code point so an astral char is one unit, never a surrogate half.
|
|
1
|
+
// Decode: content → PM, a pure function (CODEC §Decode). Fold the flat lines into the tree: group a
|
|
2
|
+
// `continues` run into one block (para hard breaks → `hard_break`; a fence's lines → one
|
|
3
|
+
// `code_block`), nest by shared `containers` prefix, select the block node by `kind`, apply marks
|
|
4
|
+
// over their `[start,end)` USV ranges, and lower island slots to leaf nodes. Anchors are
|
|
5
|
+
// decorations (field.ts). Positions are USV; `Array.from` iterates by code point so an astral char
|
|
6
|
+
// is one unit.
|
|
10
7
|
import { DOMSerializer } from 'prosemirror-model';
|
|
11
8
|
import { ISLAND_SLOT, isBlockIsland, isEditedIsland } from './islands.js';
|
|
12
9
|
import { descriptorOf, markKey, pmMarkFromContent } from './marks.js';
|
|
@@ -24,14 +21,9 @@ export function usvLength(s) {
|
|
|
24
21
|
return n;
|
|
25
22
|
}
|
|
26
23
|
/**
|
|
27
|
-
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* mark vocabulary a leaf edits it in (the tips card). A second renderer over the
|
|
31
|
-
* same content would drift from `decode`; this cannot.
|
|
32
|
-
*
|
|
33
|
-
* `DOMSerializer.fromSchema` memoizes on the schema, so repeat calls build no
|
|
34
|
-
* serializer.
|
|
24
|
+
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`, for chrome that
|
|
25
|
+
* must show content in the mark vocabulary a leaf edits it in (the tips card) without a second
|
|
26
|
+
* renderer to drift from `decode`. `DOMSerializer.fromSchema` memoizes on the schema.
|
|
35
27
|
*/
|
|
36
28
|
export function renderContent(rt, schema) {
|
|
37
29
|
return DOMSerializer.fromSchema(schema).serializeFragment(decode(rt, schema).content);
|
|
@@ -46,14 +38,12 @@ export function decode(rt, schema) {
|
|
|
46
38
|
starts.push(acc);
|
|
47
39
|
acc += usvLength(lineTexts[i]) + 1; // +1 for the `\n` boundary
|
|
48
40
|
}
|
|
49
|
-
// A mark-free schema
|
|
50
|
-
//
|
|
51
|
-
// document — is exactly the content that must open and then heal on the next
|
|
52
|
-
// commit, not throw here.
|
|
41
|
+
// A mark-free schema has nothing to apply marks with, and a `text` field that acquired one
|
|
42
|
+
// anyway (a hand-written document) must open and heal on the next commit, not throw.
|
|
53
43
|
const marks = hasMarks(schema) ? rt.marks.filter((m) => m.type !== 'anchor') : [];
|
|
54
44
|
const cursor = { i: 0, rt };
|
|
55
45
|
if (isInlineSchema(schema)) {
|
|
56
|
-
return decodeInline(
|
|
46
|
+
return decodeInline(schema, lineTexts, starts, marks, cursor);
|
|
57
47
|
}
|
|
58
48
|
if (!isBlockSchema(schema)) {
|
|
59
49
|
return decodePlain(rt, schema, lineTexts, starts, marks, cursor);
|
|
@@ -73,11 +63,11 @@ export function decode(rt, schema) {
|
|
|
73
63
|
const blocks = groupBlocks(schema, leaves, 0, marks, cursor);
|
|
74
64
|
return schema.nodes.doc.create(null, blocks.length ? blocks : schema.nodes.paragraph.create());
|
|
75
65
|
}
|
|
76
|
-
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Whether `rt` decodes under an inline schema with nothing but whitespace dropped: upstream's
|
|
68
|
+
* one-line rule, or one plain line and the empty one a trailing `\n` opens (the one a YAML `|`
|
|
69
|
+
* scalar keeps in a `text` value). Marks are the schema's business.
|
|
70
|
+
*/
|
|
81
71
|
export function fitsInline(rt) {
|
|
82
72
|
if (core().isInline(rt))
|
|
83
73
|
return true;
|
|
@@ -88,19 +78,20 @@ export function fitsInline(rt) {
|
|
|
88
78
|
!rt.lines[1].continues &&
|
|
89
79
|
rt.text.endsWith('\n'));
|
|
90
80
|
}
|
|
91
|
-
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
81
|
+
/**
|
|
82
|
+
* Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the next commit
|
|
83
|
+
* heals (CODEC §Inline mode): upstream's `isPlain` with marks set aside.
|
|
84
|
+
*/
|
|
94
85
|
export function fitsPlain(rt) {
|
|
95
86
|
return core().isPlain({ ...rt, marks: [] });
|
|
96
87
|
}
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Whether a leaf so declared edits `rt` losing nothing: {@link fitsInline} narrowed and {@link
|
|
90
|
+
* fitsPlain} plain, past which the decode drops what the first commit would store. On the block
|
|
91
|
+
* schema, anything but a construct read and not edited (`isEditedIsland`) and an island where the
|
|
92
|
+
* leaf mounts no island view (`islands`), which draws as a placeholder atom a keystroke deletes
|
|
93
|
+
* unseen.
|
|
94
|
+
*/
|
|
104
95
|
export function fitsLeaf(rt, leaf) {
|
|
105
96
|
if (leaf.inline)
|
|
106
97
|
return fitsInline(rt);
|
|
@@ -108,22 +99,13 @@ export function fitsLeaf(rt, leaf) {
|
|
|
108
99
|
return fitsPlain(rt);
|
|
109
100
|
if (!leaf.islands && rt.islands.length)
|
|
110
101
|
return false;
|
|
111
|
-
return rt.islands.every((isl) => isEditedIsland(isl.type))
|
|
112
|
-
}
|
|
113
|
-
/** Whether the block schema edits `line`'s kind and every container on it: a term draws
|
|
114
|
-
* as a paragraph and an element and a description as the blocks they wrap, so the first
|
|
115
|
-
* commit would drop each. */
|
|
116
|
-
function editable(line) {
|
|
117
|
-
if (line.kind === 'term')
|
|
118
|
-
return false;
|
|
119
|
-
return line.containers.every((c) => c.container === 'quote' || c.container === 'list_item');
|
|
102
|
+
return rt.islands.every((isl) => isEditedIsland(isl.type));
|
|
120
103
|
}
|
|
121
104
|
/** Inline decode: one paragraph, containers and islands stripped. */
|
|
122
|
-
function decodeInline(
|
|
123
|
-
// An inline field is single-line
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// are not representable inline: the slot char is dropped and its entry skipped.
|
|
105
|
+
function decodeInline(schema, lineTexts, starts, marks, cursor) {
|
|
106
|
+
// An inline field is single-line: stray lines join with a space. The empty line a trailing `\n`
|
|
107
|
+
// opens is dropped, so a first edit stores no space nobody typed. Islands are not representable
|
|
108
|
+
// inline: the slot char is dropped and its entry skipped.
|
|
127
109
|
const inline = [];
|
|
128
110
|
const last = lineTexts.length - 1;
|
|
129
111
|
for (let i = 0; i < lineTexts.length; i++) {
|
|
@@ -136,9 +118,11 @@ function decodeInline(rt, schema, lineTexts, starts, marks, cursor) {
|
|
|
136
118
|
const para = schema.nodes.paragraph.create(null, inline);
|
|
137
119
|
return schema.nodes.doc.create(null, para);
|
|
138
120
|
}
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
121
|
+
/**
|
|
122
|
+
* Plain decode: a `continues` line is a `hard_break` in the paragraph above and every other line
|
|
123
|
+
* opens one. Line kinds, containers and islands are dropped, so content {@link fitsPlain} refuses
|
|
124
|
+
* opens flattened.
|
|
125
|
+
*/
|
|
142
126
|
function decodePlain(rt, schema, lineTexts, starts, marks, cursor) {
|
|
143
127
|
const paras = [];
|
|
144
128
|
for (let i = 0; i < rt.lines.length; i++) {
|
|
@@ -198,25 +182,9 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
198
182
|
i = j;
|
|
199
183
|
continue;
|
|
200
184
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const key = containerKey(here);
|
|
205
|
-
let j = i + 1;
|
|
206
|
-
while (j < leaves.length) {
|
|
207
|
-
const c = atDepth(leaves[j], depth);
|
|
208
|
-
if (!c || containerKey(c) !== key)
|
|
209
|
-
break;
|
|
210
|
-
j++;
|
|
211
|
-
}
|
|
212
|
-
out.push(...groupBlocks(schema, leaves.slice(i, j), depth + 1, marks, cursor));
|
|
213
|
-
i = j;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
// The quote, and the whole of what is left: `satisfies` is the exhaustiveness
|
|
217
|
-
// check, so a container added upstream is a compile error here rather than a
|
|
218
|
-
// blockquote it is not. A wrapper over the run of leaves carrying the identical
|
|
219
|
-
// container here, identity by `containerKey`.
|
|
185
|
+
// The quote, and the whole of what is left: `satisfies` makes a container added upstream a
|
|
186
|
+
// compile error. A wrapper over the run of leaves carrying the identical container
|
|
187
|
+
// (`containerKey`).
|
|
220
188
|
here.container;
|
|
221
189
|
const key = containerKey(here);
|
|
222
190
|
let j = i + 1;
|
|
@@ -235,8 +203,10 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
235
203
|
function atDepth(leaf, depth) {
|
|
236
204
|
return leaf.containers[depth];
|
|
237
205
|
}
|
|
238
|
-
/**
|
|
239
|
-
*
|
|
206
|
+
/**
|
|
207
|
+
* The `ordinal` of a leaf's `list_item` at `depth`, only called inside a run the discriminant
|
|
208
|
+
* established.
|
|
209
|
+
*/
|
|
240
210
|
function ordinalAt(leaf, depth) {
|
|
241
211
|
const c = atDepth(leaf, depth);
|
|
242
212
|
return c?.container === 'list_item' ? c.attrs.ordinal : -1;
|
|
@@ -247,22 +217,23 @@ function checkedAt(leaf, depth) {
|
|
|
247
217
|
const c = atDepth(leaf, depth);
|
|
248
218
|
return c?.container === 'list_item' ? (c.attrs.checked ?? null) : null;
|
|
249
219
|
}
|
|
250
|
-
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
220
|
+
/**
|
|
221
|
+
* A container's `instance`, absent meaning the zero a canonical read omits. It tells one container
|
|
222
|
+
* from an adjacent sibling of identical shape, so absent and `0` answer alike.
|
|
223
|
+
*/
|
|
253
224
|
function instanceOf(c) {
|
|
254
225
|
return c.instance ?? 0;
|
|
255
226
|
}
|
|
256
|
-
/** Identity of a container for run gathering: its name
|
|
257
|
-
*
|
|
258
|
-
*
|
|
227
|
+
/** Identity of a container for run gathering: its name and its `instance`, NUL-joined
|
|
228
|
+
* as `markKey` joins a mark's. `list_item` carries its own run rule, so the rest are what
|
|
229
|
+
* key here. */
|
|
259
230
|
function containerKey(c) {
|
|
260
|
-
|
|
261
|
-
return `${c.container}\u0000${instanceOf(c)}\u0000${attrs}`;
|
|
231
|
+
return `${c.container}\u0000${instanceOf(c)}`;
|
|
262
232
|
}
|
|
263
|
-
/**
|
|
264
|
-
*
|
|
265
|
-
*
|
|
233
|
+
/**
|
|
234
|
+
* A single leaf block node from its segments, exhaustive over the closed line vocabulary so a kind
|
|
235
|
+
* added upstream is a compile error.
|
|
236
|
+
*/
|
|
266
237
|
function makeLeaf(schema, leaf, marks, cursor) {
|
|
267
238
|
const line = leaf.line;
|
|
268
239
|
switch (line.kind) {
|
|
@@ -275,15 +246,12 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
275
246
|
return schema.nodes.code_block.create({ lang: line.attrs?.lang ?? null }, content);
|
|
276
247
|
}
|
|
277
248
|
case 'para':
|
|
278
|
-
case 'term':
|
|
279
249
|
case 'heading': {
|
|
280
250
|
if (line.kind !== 'heading' && blockIslandLine(leaf, cursor))
|
|
281
251
|
return schema.nodes.island_block.create(islandAttrs(cursor));
|
|
282
|
-
// Inline content
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// so the space is what keeps a hand-built content decodable. A term draws as a
|
|
286
|
-
// paragraph, read-only (`fitsLeaf`).
|
|
252
|
+
// Inline content: the segment boundary is a `hard_break` in a paragraph and a space in
|
|
253
|
+
// a heading, which takes none (`schema.ts` §`takesLineBreak`). A stored heading carries
|
|
254
|
+
// no continuation, so the space keeps hand-built content decodable.
|
|
287
255
|
const type = line.kind === 'heading' ? schema.nodes.heading : schema.nodes.paragraph;
|
|
288
256
|
const inline = [];
|
|
289
257
|
leaf.segments.forEach((seg, idx) => {
|
|
@@ -301,29 +269,29 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
301
269
|
}
|
|
302
270
|
}
|
|
303
271
|
}
|
|
304
|
-
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
272
|
+
/**
|
|
273
|
+
* Whether a `para` leaf is one block island: a single line holding its slot alone, backed by an
|
|
274
|
+
* island whose markup is a block.
|
|
275
|
+
*/
|
|
307
276
|
function blockIslandLine(leaf, cursor) {
|
|
308
277
|
if (leaf.segments.length !== 1 || leaf.segments[0].text !== ISLAND_SLOT)
|
|
309
278
|
return false;
|
|
310
279
|
const isl = cursor.rt.islands[cursor.i];
|
|
311
280
|
return isl != null && isBlockIsland(isl.type);
|
|
312
281
|
}
|
|
313
|
-
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Consume the next island entry as PM node attrs (text-order matched to slots): the whole entry,
|
|
284
|
+
* which an island edit writes back (`islands.ts`). `null` where a slot has no entry (malformed
|
|
285
|
+
* content): the slot draws nothing, keeping the decode total.
|
|
286
|
+
*/
|
|
318
287
|
function islandAttrs(cursor) {
|
|
319
288
|
const isl = cursor.rt.islands[cursor.i++];
|
|
320
289
|
return isl ? { id: isl.id, islandType: isl.type, props: isl.props } : null;
|
|
321
290
|
}
|
|
322
291
|
/**
|
|
323
|
-
* Inline nodes for one segment: split text into maximal runs of a constant mark
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* global mark ranges.
|
|
292
|
+
* Inline nodes for one segment: split text into maximal runs of a constant mark set, lower each
|
|
293
|
+
* `U+FFFC` to an inline island node. `stripIslands` drops slots. `pos = segStartUSV + k` indexes
|
|
294
|
+
* into the global mark ranges.
|
|
327
295
|
*/
|
|
328
296
|
function buildInline(schema, segText, segStartUSV, marks, cursor, stripIslands) {
|
|
329
297
|
const cps = codePoints(segText);
|
|
@@ -336,9 +304,8 @@ function buildInline(schema, segText, segStartUSV, marks, cursor, stripIslands)
|
|
|
336
304
|
out.push(schema.text(runText, runMarks));
|
|
337
305
|
runText = '';
|
|
338
306
|
};
|
|
339
|
-
// A mark set changes only where a mark starts or ends
|
|
340
|
-
//
|
|
341
|
-
// document.
|
|
307
|
+
// A mark set changes only where a mark starts or ends; `marks` is the whole list, so resolving
|
|
308
|
+
// per code point would cost the text against every mark.
|
|
342
309
|
const segEndUSV = segStartUSV + cps.length;
|
|
343
310
|
const spanning = marks.filter((m) => m.start < segEndUSV && m.end > segStartUSV);
|
|
344
311
|
const edges = new Set();
|
|
@@ -384,10 +351,8 @@ function buildInline(schema, segText, segStartUSV, marks, cursor, stripIslands)
|
|
|
384
351
|
function markSetKey(active) {
|
|
385
352
|
if (!active.length)
|
|
386
353
|
return '';
|
|
387
|
-
// Per-mark keys via the shared descriptor + NUL-delimited `markKey
|
|
388
|
-
//
|
|
389
|
-
// one rule. The set is JSON-joined so no url/attrs content collides with a
|
|
390
|
-
// delimiter: a `link:a|strong` url stays distinct from `{link:a} + {strong}`.
|
|
354
|
+
// Per-mark keys via the shared descriptor + NUL-delimited `markKey`, the pair the mark diff
|
|
355
|
+
// groups by. JSON-joined so a `link:a|strong` url stays distinct from `{link:a} + {strong}`.
|
|
391
356
|
return JSON.stringify(active.map((m) => markKey(descriptorOf(m))).sort());
|
|
392
357
|
}
|
|
393
358
|
/** The PM mark array for an active content mark set (anchors already excluded). */
|
|
@@ -25,13 +25,14 @@ export interface Scan {
|
|
|
25
25
|
usvEnd: number;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Walk a PM document once, producing the content projection and the position-map
|
|
29
|
-
*
|
|
30
|
-
* source both `pmToContent` and the position map draw from.
|
|
28
|
+
* Walk a PM document once, producing the content projection and the position-map runs; sharing the
|
|
29
|
+
* traversal means they cannot disagree.
|
|
31
30
|
*/
|
|
32
31
|
export declare function scanDoc(doc: PMNode): Scan;
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
32
|
+
/**
|
|
33
|
+
* The content projection a scan carries, for a caller holding a `Scan` that would otherwise walk
|
|
34
|
+
* again.
|
|
35
|
+
*/
|
|
35
36
|
export declare function scanContent(s: Scan): Content;
|
|
36
37
|
/** `pmToContent`: the pure inverse of decode (drops the position runs). */
|
|
37
38
|
export declare function pmToContent(doc: PMNode): Content;
|
|
@@ -44,16 +45,11 @@ interface LowerOpts {
|
|
|
44
45
|
}[];
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
|
-
* One old→new content edit with the text splice it implies. Both sides are content,
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* The splice rides along because `diffText` allocates a code-point array per side
|
|
53
|
-
* and the commit path holds the edit across the lowering. `contentEdit` being its
|
|
54
|
-
* only constructor is what keeps a `delta` from disagreeing with the contents it
|
|
55
|
-
* was diffed from; `lower` is where a slot inside that splice moves to the island
|
|
56
|
-
* channel, so this `delta` is the raw splice, not always the one committed.
|
|
48
|
+
* One old→new content edit with the text splice it implies. Both sides are content, never a PM doc:
|
|
49
|
+
* a doc-taking overload would re-walk the tree per keystroke for a projection the caller holds. The
|
|
50
|
+
* splice rides along because `diffText` allocates a code-point array per side. `contentEdit` is its
|
|
51
|
+
* only constructor, which keeps a `delta` agreeing with the contents it was diffed from; `lower`
|
|
52
|
+
* moves a slot inside the splice to the island channel, so this `delta` is the raw splice.
|
|
57
53
|
*/
|
|
58
54
|
export interface ContentEdit {
|
|
59
55
|
readonly oldRt: Content;
|