@quillmark/svelte 0.10.1 → 0.12.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 +1 -1
- package/THEMING.md +2 -2
- package/dist/core/address.d.ts +17 -10
- package/dist/core/address.js +26 -12
- package/dist/core/bloom.js +4 -3
- package/dist/core/codec/blocks.js +21 -1
- package/dist/core/codec/breaks.d.ts +10 -2
- package/dist/core/codec/breaks.js +65 -9
- package/dist/core/codec/code.d.ts +6 -4
- package/dist/core/codec/code.js +7 -4
- package/dist/core/codec/decode.js +103 -68
- package/dist/core/codec/encode.d.ts +5 -8
- package/dist/core/codec/encode.js +56 -86
- package/dist/core/codec/field.js +43 -24
- package/dist/core/codec/index.d.ts +2 -1
- package/dist/core/codec/index.js +6 -3
- package/dist/core/codec/islands.d.ts +16 -11
- package/dist/core/codec/islands.js +70 -59
- package/dist/core/codec/keymap.js +2 -1
- package/dist/core/codec/marks.d.ts +17 -13
- package/dist/core/codec/marks.js +39 -42
- package/dist/core/codec/positions.d.ts +4 -1
- package/dist/core/codec/positions.js +5 -1
- package/dist/core/codec/reconcile.d.ts +6 -0
- package/dist/core/codec/reconcile.js +19 -0
- package/dist/core/codec/schema.d.ts +5 -8
- package/dist/core/codec/schema.js +30 -156
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.js +77 -26
- package/dist/core/codec/table.d.ts +6 -5
- package/dist/core/codec/table.js +14 -31
- package/dist/core/codec/urls.d.ts +20 -0
- package/dist/core/codec/urls.js +45 -0
- package/dist/core/rungs.d.ts +8 -0
- package/dist/core/rungs.js +24 -0
- package/dist/core/theme.css +10 -0
- package/dist/preset/recipes.css +1 -1
- package/dist/preset/scale.css +7 -4
- package/dist/preview/bridge.d.ts +8 -1
- package/dist/preview/bridge.js +31 -25
- package/dist/preview/controller.js +13 -13
- package/dist/preview/geometry.d.ts +12 -1
- package/dist/preview/geometry.js +14 -1
- package/dist/preview/paint.js +6 -4
- package/dist/preview/strings.d.ts +0 -2
- package/dist/preview/strings.js +5 -6
- package/dist/visual/ArrayField.svelte +600 -173
- package/dist/visual/ArrayField.svelte.d.ts +14 -1
- package/dist/visual/BooleanField.svelte +10 -7
- package/dist/visual/Card.svelte +8 -30
- package/dist/visual/DateField.svelte +2 -2
- package/dist/visual/Field.svelte +76 -32
- package/dist/visual/FieldHint.svelte +1 -1
- package/dist/visual/MatrixField.svelte +352 -0
- package/dist/visual/MatrixField.svelte.d.ts +36 -0
- package/dist/visual/ObjectField.svelte +356 -133
- package/dist/visual/ObjectField.svelte.d.ts +17 -2
- package/dist/visual/VariantField.svelte +55 -2
- package/dist/visual/VariantField.svelte.d.ts +10 -1
- package/dist/visual/VisualEditor.svelte +1 -1
- package/dist/visual/VisualEditorInner.svelte +118 -67
- package/dist/visual/controls.css +39 -9
- package/dist/visual/diagnostics.d.ts +26 -2
- package/dist/visual/diagnostics.js +52 -5
- package/dist/visual/ext.d.ts +12 -13
- package/dist/visual/ext.js +22 -19
- package/dist/visual/hold.js +2 -2
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/index.js +2 -3
- package/dist/visual/leaves.d.ts +25 -12
- package/dist/visual/links.d.ts +4 -0
- package/dist/visual/links.js +15 -4
- package/dist/visual/motion.d.ts +14 -5
- package/dist/visual/motion.js +13 -11
- package/dist/visual/strings.d.ts +26 -13
- package/dist/visual/strings.js +7 -2
- package/dist/visual/structure.d.ts +101 -8
- package/dist/visual/structure.js +252 -13
- package/package.json +2 -2
|
@@ -1,35 +1,29 @@
|
|
|
1
|
-
// Islands: the `U+FFFC` slot + `ContentIsland` entry ↔ a PM leaf node.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// `props` verbatim, so an unknown island type round-trips untouched.
|
|
1
|
+
// Islands: the `U+FFFC` slot + `ContentIsland` entry ↔ a PM leaf node. Every slot sits
|
|
2
|
+
// in a `para` line; the island's type says which node it is, a `table` a block alone on
|
|
3
|
+
// its line and an `image` inline. The node carries `id`, `islandType` and `props` verbatim.
|
|
5
4
|
//
|
|
6
5
|
// A props-aware consumer reads the shape straight off the boundary:
|
|
7
6
|
// `ContentIsland.props` is the typed union (`TableProps` for `table`,
|
|
8
7
|
// `ImageProps` for `image`; DOCUMENT_MODEL §Stability seams), so the codec
|
|
9
|
-
// reads it rather than hand-rolling a shape
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// reads it rather than hand-rolling a shape. `type` is a closed set, so a
|
|
9
|
+
// discriminant check narrows it; `props` still crosses the DOM as opaque JSON,
|
|
10
|
+
// which is what §`tablePropsOfNode` guards.
|
|
12
11
|
import { Fragment, Slice } from 'prosemirror-model';
|
|
13
12
|
import { Plugin } from 'prosemirror-state';
|
|
14
|
-
import {
|
|
13
|
+
import { storableUrl } from './urls.js';
|
|
15
14
|
/** The `U+FFFC` object-replacement char that occupies one island slot in `text`. */
|
|
16
15
|
export const ISLAND_SLOT = '';
|
|
17
|
-
// The
|
|
18
|
-
// stable identity (rebases like an anchor), `islandType` the discriminator, `props`
|
|
19
|
-
// the
|
|
20
|
-
// `
|
|
21
|
-
//
|
|
22
|
-
// an entry that did not carry its own would promote a degraded table to lossless
|
|
23
|
-
// on the first cell edit. The default is the class an unknown one reads as, so a
|
|
24
|
-
// node no decode produced under-claims rather than over-claims; decode always
|
|
25
|
-
// supplies the stored value.
|
|
16
|
+
// The three island node attributes carry the content entry verbatim: `id` is the
|
|
17
|
+
// stable identity (rebases like an anchor), `islandType` the discriminator, and `props`
|
|
18
|
+
// the payload the codec preserves byte-for-byte across a round-trip. `islandType` and
|
|
19
|
+
// `props` declare no default: a node standing for an island holds one, so the pair is
|
|
20
|
+
// supplied at every `create`.
|
|
26
21
|
const islandAttrs = {
|
|
27
22
|
id: { default: '' },
|
|
28
|
-
islandType: {
|
|
29
|
-
props: {
|
|
30
|
-
loss: { default: 'unrepresentable' }
|
|
23
|
+
islandType: {},
|
|
24
|
+
props: {}
|
|
31
25
|
};
|
|
32
|
-
// All
|
|
26
|
+
// All three attributes cross the DOM: a copy and a paste inside one body run the document
|
|
33
27
|
// through `toDOM`/`parseDOM` (CODEC §"Markdown at the edges"), and an island is a leaf
|
|
34
28
|
// whose tags alone say only `[table]`. `props` crosses as JSON, the form the payload has
|
|
35
29
|
// at the boundary. The names are `data-qm-*` like every other carrier's, and the id is
|
|
@@ -40,17 +34,29 @@ function islandDOM(node) {
|
|
|
40
34
|
return {
|
|
41
35
|
'data-qm-island': node.attrs.islandType,
|
|
42
36
|
'data-qm-island-id': node.attrs.id,
|
|
43
|
-
'data-qm-island-loss': node.attrs.loss,
|
|
44
37
|
'data-qm-island-props': JSON.stringify(node.attrs.props ?? null)
|
|
45
38
|
};
|
|
46
39
|
}
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
40
|
+
/** The island types the content model names, each with whether its markup is a block,
|
|
41
|
+
* which decides the node its slot decodes to. A `Record` over the boundary's own union,
|
|
42
|
+
* so a member added upstream is a missing key here rather than a silent refusal at the
|
|
43
|
+
* one door with no decode behind it. */
|
|
44
|
+
const BLOCK_ISLAND = { table: true, image: false };
|
|
45
|
+
/** Whether an island of `type` is block markup: a `table` stands alone on its line. */
|
|
46
|
+
export function isBlockIsland(type) {
|
|
47
|
+
return BLOCK_ISLAND[type];
|
|
48
|
+
}
|
|
49
|
+
/** An island node's attributes off that DOM, or `false` where the id is absent or the
|
|
50
|
+
* type is one the content vocabulary does not name — a name no read produces and no
|
|
51
|
+
* write takes, so the element's text arrives as text. A `props` that is not the JSON
|
|
52
|
+
* written above reads as `null`, and one of the wrong shape is `undefined` at its
|
|
53
|
+
* reader (§`tablePropsOfNode`): the entry survives as an island of its type rather
|
|
54
|
+
* than taking the paste down with it. */
|
|
51
55
|
function islandAttrsFromDOM(el) {
|
|
52
56
|
const id = el.getAttribute('data-qm-island-id');
|
|
53
|
-
|
|
57
|
+
// Asserted off an untrusted attribute and checked against the set on the next line.
|
|
58
|
+
const type = el.getAttribute('data-qm-island');
|
|
59
|
+
if (id == null || type == null || !Object.hasOwn(BLOCK_ISLAND, type))
|
|
54
60
|
return false;
|
|
55
61
|
const raw = el.getAttribute('data-qm-island-props');
|
|
56
62
|
let props = null;
|
|
@@ -60,14 +66,18 @@ function islandAttrsFromDOM(el) {
|
|
|
60
66
|
catch {
|
|
61
67
|
props = null;
|
|
62
68
|
}
|
|
63
|
-
return {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
return { id, islandType: type, props: storableProps(type, props) };
|
|
70
|
+
}
|
|
71
|
+
/** `props` with the one key the authored lane refuses made storable: an `image`'s `url`
|
|
72
|
+
* carrying a line ending, which `IslandOp` and `overwrite` throw on. The rest crosses
|
|
73
|
+
* opaque, as it did on the way out. */
|
|
74
|
+
function storableProps(type, props) {
|
|
75
|
+
if (type !== 'image' || typeof props !== 'object' || props === null)
|
|
76
|
+
return props;
|
|
77
|
+
const url = props.url;
|
|
78
|
+
return typeof url === 'string' ? { ...props, url: storableUrl(url) } : props;
|
|
69
79
|
}
|
|
70
|
-
/** Block island node (a table):
|
|
80
|
+
/** Block island node (a table): a `U+FFFC` slot alone on its `para` line. Atom, unselectable content. */
|
|
71
81
|
export const islandBlockSpec = {
|
|
72
82
|
group: 'block',
|
|
73
83
|
atom: true,
|
|
@@ -86,13 +96,16 @@ export const islandInlineSpec = {
|
|
|
86
96
|
parseDOM: [{ tag: 'span[data-qm-island]', getAttrs: (el) => islandAttrsFromDOM(el) }],
|
|
87
97
|
toDOM: (node) => ['span', islandDOM(node), `[${node.attrs.islandType || 'island'}]`]
|
|
88
98
|
};
|
|
89
|
-
/** Build a content island entry from a PM island node's attrs (block or inline).
|
|
99
|
+
/** Build a content island entry from a PM island node's attrs (block or inline). The
|
|
100
|
+
* cast pairs a closed `type` with a `props` that crossed the DOM as opaque JSON. The
|
|
101
|
+
* store is lenient about a table's shape — it fills what it can and keeps the rest —
|
|
102
|
+
* so a payload the arm does not hold is stored rather than refused, and `undefined` at
|
|
103
|
+
* its reader is what the surface draws the placeholder for (§`tablePropsOfNode`). */
|
|
90
104
|
export function islandEntryFromNode(attrs) {
|
|
91
105
|
return {
|
|
92
106
|
id: attrs.id,
|
|
93
107
|
type: attrs.islandType,
|
|
94
|
-
props: attrs.props
|
|
95
|
-
loss: attrs.loss
|
|
108
|
+
props: attrs.props
|
|
96
109
|
};
|
|
97
110
|
}
|
|
98
111
|
/** The `TableProps` shape, over a value that reached the node off the DOM rather than
|
|
@@ -107,13 +120,13 @@ function isTableProps(props) {
|
|
|
107
120
|
return (cells(header) && Array.isArray(aligns) && Array.isArray(rows) && rows.every((row) => cells(row)));
|
|
108
121
|
}
|
|
109
122
|
/** A node's `TableProps`, or `undefined` for any other island and for a payload of the
|
|
110
|
-
* wrong shape: the boundary's own guard over the entry the node carries, so
|
|
111
|
-
*
|
|
112
|
-
* the shape says whether it can read; every caller draws the placeholder
|
|
113
|
-
* `undefined` (`table-view.ts` §`render`). */
|
|
123
|
+
* wrong shape: the boundary's own guard over the entry the node carries, so a `props`
|
|
124
|
+
* that crossed the DOM is read once here rather than at each reader. The type says
|
|
125
|
+
* which reader, the shape says whether it can read; every caller draws the placeholder
|
|
126
|
+
* for `undefined` (`table-view.ts` §`render`). */
|
|
114
127
|
export function tablePropsOfNode(node) {
|
|
115
|
-
const
|
|
116
|
-
return
|
|
128
|
+
const attrs = node.attrs;
|
|
129
|
+
return attrs.islandType === 'table' && isTableProps(attrs.props) ? attrs.props : undefined;
|
|
117
130
|
}
|
|
118
131
|
/**
|
|
119
132
|
* A minter over one document: the positional `isl-{n}` sequence continued past the
|
|
@@ -128,38 +141,35 @@ export function tablePropsOfNode(node) {
|
|
|
128
141
|
* a stale content could collide with one the same transaction places.
|
|
129
142
|
*/
|
|
130
143
|
export function islandMinter(doc) {
|
|
144
|
+
return islandState(doc).mint;
|
|
145
|
+
}
|
|
146
|
+
/** The ids a document holds and the minter continuing past them, off one sweep: both
|
|
147
|
+
* are the same walk under the same predicate, and the paste pass wants both. */
|
|
148
|
+
function islandState(doc) {
|
|
149
|
+
const held = new Set();
|
|
131
150
|
let next = 0;
|
|
132
151
|
doc.descendants((node) => {
|
|
133
152
|
if (!isIsland(node))
|
|
134
153
|
return true;
|
|
135
|
-
const
|
|
154
|
+
const id = node.attrs.id;
|
|
155
|
+
held.add(id);
|
|
156
|
+
const m = /^isl-(\d+)$/.exec(id);
|
|
136
157
|
if (m)
|
|
137
158
|
next = Math.max(next, Number(m[1]) + 1);
|
|
138
159
|
return false;
|
|
139
160
|
});
|
|
140
|
-
return () => `isl-${next++}
|
|
161
|
+
return { held, mint: () => `isl-${next++}` };
|
|
141
162
|
}
|
|
142
163
|
/** The next island id for a field: one draw from {@link islandMinter}, which is what a
|
|
143
164
|
* command placing exactly one island wants. */
|
|
144
165
|
export function mintIslandId(doc) {
|
|
145
166
|
return islandMinter(doc)();
|
|
146
167
|
}
|
|
147
|
-
/** Either island node: the test the
|
|
148
|
-
*
|
|
168
|
+
/** Either island node: the test the sweep and the paste pass share, so a third island
|
|
169
|
+
* shape is one arm rather than two. */
|
|
149
170
|
function isIsland(node) {
|
|
150
171
|
return node.type.name === 'island_block' || node.type.name === 'island_inline';
|
|
151
172
|
}
|
|
152
|
-
/** The ids a document already holds. */
|
|
153
|
-
function heldIds(doc) {
|
|
154
|
-
const held = new Set();
|
|
155
|
-
doc.descendants((node) => {
|
|
156
|
-
if (!isIsland(node))
|
|
157
|
-
return true;
|
|
158
|
-
held.add(node.attrs.id);
|
|
159
|
-
return false;
|
|
160
|
-
});
|
|
161
|
-
return held;
|
|
162
|
-
}
|
|
163
173
|
/** `fragment` with every island whose id is already `taken` re-minted, or `null` where
|
|
164
174
|
* none was: an untouched paste stays the slice it arrived as. Every surviving id joins
|
|
165
175
|
* the set, so a slice carrying one id twice comes apart in the same pass. */
|
|
@@ -205,7 +215,8 @@ export function islandPastePlugin() {
|
|
|
205
215
|
return new Plugin({
|
|
206
216
|
props: {
|
|
207
217
|
transformPasted: (slice, view) => {
|
|
208
|
-
const
|
|
218
|
+
const { held, mint } = islandState(view.state.doc);
|
|
219
|
+
const content = remintIslands(slice.content, held, mint);
|
|
209
220
|
return content ? new Slice(content, slice.openStart, slice.openEnd) : slice;
|
|
210
221
|
}
|
|
211
222
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chainCommands } from 'prosemirror-commands';
|
|
2
2
|
import { atomKeymap } from './atoms.js';
|
|
3
3
|
import { blockKeymap } from './blocks.js';
|
|
4
|
+
import { breakKeymap } from './breaks.js';
|
|
4
5
|
import { codeKeymap } from './code.js';
|
|
5
6
|
import { listKeymap } from './lists.js';
|
|
6
7
|
import { slashKeymap } from './slash.js';
|
|
@@ -27,5 +28,5 @@ function chainKeymaps(...maps) {
|
|
|
27
28
|
* no visible reason. */
|
|
28
29
|
export function bodyKeymap(schema, slash) {
|
|
29
30
|
const links = slash ? [slashKeymap()] : [];
|
|
30
|
-
return chainKeymaps(...links, blockKeymap(schema), codeKeymap(schema), listKeymap(schema), atomKeymap(schema));
|
|
31
|
+
return chainKeymaps(...links, blockKeymap(schema), codeKeymap(schema), breakKeymap(schema), listKeymap(schema), atomKeymap(schema));
|
|
31
32
|
}
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import type { Mark, Schema } from 'prosemirror-model';
|
|
2
|
-
import type { ContentMark } from '@quillmark/wasm';
|
|
3
|
-
/** A PM mark from a content formatting
|
|
2
|
+
import type { ContentMark, ContentMarkKind } from '@quillmark/wasm';
|
|
3
|
+
/** A PM mark from a content formatting mark, or `null` for an anchor. Exhaustive over
|
|
4
|
+
* the closed mark vocabulary, so a member added upstream is a compile error here rather
|
|
5
|
+
* than an index into a PM mark this schema does not declare. */
|
|
4
6
|
export declare function pmMarkFromContent(schema: Schema, m: ContentMark): Mark | null;
|
|
5
7
|
/**
|
|
6
8
|
* A content mark descriptor from a PM mark (range-free): the `{ type, … }` half
|
|
7
|
-
* of a `ContentMark` / `MarkOp`. `
|
|
8
|
-
*
|
|
9
|
+
* of a `ContentMark` / `MarkOp`. `em` collapses to `emph` and a link's `href` rides
|
|
10
|
+
* out as the `url` its payload spells; the other four are their own content name,
|
|
11
|
+
* which is what the cast claims.
|
|
9
12
|
*/
|
|
10
|
-
export declare function contentDescriptorFromPM(mark: Mark):
|
|
13
|
+
export declare function contentDescriptorFromPM(mark: Mark): ContentMarkKind;
|
|
11
14
|
/**
|
|
12
15
|
* The range-free `{ type, … }` half of a content mark: `contentDescriptorFromPM`'s
|
|
13
|
-
* content-side twin, and what a `MarkOp` carries beside its range.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
* content-side twin, and what a `MarkOp` carries beside its range. Lifted off the
|
|
17
|
+
* range envelope rather than rebuilt arm by arm, so a payload crosses whole and an
|
|
18
|
+
* arm added upstream reaches this by construction.
|
|
16
19
|
*/
|
|
17
|
-
export declare function descriptorOf(m: ContentMark):
|
|
20
|
+
export declare function descriptorOf(m: ContentMark): ContentMarkKind;
|
|
18
21
|
/**
|
|
19
22
|
* A stable grouping key for the mark diff: marks sharing a key union into one
|
|
20
|
-
* coverage set.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* coverage set. Payload-free formatting keys on its type, everything else on
|
|
24
|
+
* type+attrs, because `applyChange`'s `remove` matches type and attrs (verified),
|
|
25
|
+
* so two links differing in url are independent mark families. This is the seam's
|
|
26
|
+
* own `(type, attrs)` tie-break, which is why no arm needs a case of its own.
|
|
23
27
|
*/
|
|
24
|
-
export declare function markKey(descriptor:
|
|
28
|
+
export declare function markKey(descriptor: ContentMarkKind): string;
|
|
25
29
|
/** A held anchor position: an identity id at a USV content offset (zero-width). */
|
|
26
30
|
export interface AnchorPos {
|
|
27
31
|
id: string;
|
package/dist/core/codec/marks.js
CHANGED
|
@@ -1,64 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { canonicalJson } from './reconcile.js';
|
|
2
|
+
/** A PM mark from a content formatting mark, or `null` for an anchor. Exhaustive over
|
|
3
|
+
* the closed mark vocabulary, so a member added upstream is a compile error here rather
|
|
4
|
+
* than an index into a PM mark this schema does not declare. */
|
|
5
5
|
export function pmMarkFromContent(schema, m) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
switch (m.type) {
|
|
7
|
+
case 'anchor':
|
|
8
|
+
return null;
|
|
9
|
+
case 'emph':
|
|
10
|
+
return schema.marks.em.create();
|
|
11
|
+
case 'link':
|
|
12
|
+
return schema.marks.link.create({ href: m.attrs.url });
|
|
13
|
+
case 'strong':
|
|
14
|
+
case 'underline':
|
|
15
|
+
case 'strike':
|
|
16
|
+
case 'code':
|
|
17
|
+
return schema.marks[m.type].create();
|
|
18
|
+
default: {
|
|
19
|
+
const unreached = m;
|
|
20
|
+
return unreached;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* A content mark descriptor from a PM mark (range-free): the `{ type, … }` half
|
|
22
|
-
* of a `ContentMark` / `MarkOp`. `
|
|
23
|
-
*
|
|
26
|
+
* of a `ContentMark` / `MarkOp`. `em` collapses to `emph` and a link's `href` rides
|
|
27
|
+
* out as the `url` its payload spells; the other four are their own content name,
|
|
28
|
+
* which is what the cast claims.
|
|
24
29
|
*/
|
|
25
30
|
export function contentDescriptorFromPM(mark) {
|
|
26
31
|
const name = mark.type.name;
|
|
27
32
|
if (name === 'em')
|
|
28
33
|
return { type: 'emph' };
|
|
29
34
|
if (name === 'link')
|
|
30
|
-
return { type: 'link', url: mark.attrs.href };
|
|
31
|
-
if (name === 'unknown')
|
|
32
|
-
return { type: mark.attrs.type, attrs: mark.attrs.attrs };
|
|
33
|
-
// strong / underline / strike / code
|
|
35
|
+
return { type: 'link', attrs: { url: mark.attrs.href } };
|
|
34
36
|
return { type: name };
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* The range-free `{ type, … }` half of a content mark: `contentDescriptorFromPM`'s
|
|
38
|
-
* content-side twin, and what a `MarkOp` carries beside its range.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* content-side twin, and what a `MarkOp` carries beside its range. Lifted off the
|
|
41
|
+
* range envelope rather than rebuilt arm by arm, so a payload crosses whole and an
|
|
42
|
+
* arm added upstream reaches this by construction.
|
|
41
43
|
*/
|
|
42
44
|
export function descriptorOf(m) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if ('attrs' in m && !isAnchorMark(m))
|
|
46
|
-
return { type: m.type, attrs: m.attrs };
|
|
47
|
-
return { type: m.type };
|
|
45
|
+
const { start: _start, end: _end, ...kind } = m;
|
|
46
|
+
return kind;
|
|
48
47
|
}
|
|
49
48
|
/**
|
|
50
49
|
* A stable grouping key for the mark diff: marks sharing a key union into one
|
|
51
|
-
* coverage set.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* coverage set. Payload-free formatting keys on its type, everything else on
|
|
51
|
+
* type+attrs, because `applyChange`'s `remove` matches type and attrs (verified),
|
|
52
|
+
* so two links differing in url are independent mark families. This is the seam's
|
|
53
|
+
* own `(type, attrs)` tie-break, which is why no arm needs a case of its own.
|
|
54
54
|
*/
|
|
55
55
|
export function markKey(descriptor) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (descriptor.attrs !== undefined)
|
|
60
|
-
return `${type}\u0000${JSON.stringify(descriptor.attrs)}`;
|
|
61
|
-
return type;
|
|
56
|
+
if ('attrs' in descriptor)
|
|
57
|
+
return `${descriptor.type}\u0000${canonicalJson(descriptor.attrs)}`;
|
|
58
|
+
return descriptor.type;
|
|
62
59
|
}
|
|
63
60
|
/**
|
|
64
61
|
* The identity anchors of a `Content` as `{ id, pos }` in USV: the seed for the
|
|
@@ -68,7 +65,7 @@ export function markKey(descriptor) {
|
|
|
68
65
|
export function anchorsFromContent(rt) {
|
|
69
66
|
const out = [];
|
|
70
67
|
for (const m of rt.marks)
|
|
71
|
-
if (
|
|
72
|
-
out.push({ id: m.id, pos: m.start });
|
|
68
|
+
if (m.type === 'anchor')
|
|
69
|
+
out.push({ id: m.attrs.id, pos: m.start });
|
|
73
70
|
return out;
|
|
74
71
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Node as PMNode } from 'prosemirror-model';
|
|
2
|
-
import { type PosRun } from './encode.js';
|
|
2
|
+
import { type PosRun, type Scan } from './encode.js';
|
|
3
3
|
/** A prebuilt line→position index for a PM doc, rebuilt on structural change. */
|
|
4
4
|
export interface LineIndex {
|
|
5
5
|
runs: PosRun[];
|
|
@@ -11,6 +11,9 @@ export interface LineIndex {
|
|
|
11
11
|
}
|
|
12
12
|
/** Build the position index for `doc` (walks it once via the shared scanner). */
|
|
13
13
|
export declare function buildLineIndex(doc: PMNode): LineIndex;
|
|
14
|
+
/** The index a scan's runs make. The commit path takes this one: it wants the
|
|
15
|
+
* content projection off the same walk (`encode.ts`, `scanContent`). */
|
|
16
|
+
export declare function lineIndexOf(s: Scan): LineIndex;
|
|
14
17
|
/** USV content offset → PM position. */
|
|
15
18
|
export declare function usvToPM(index: LineIndex, usvPos: number): number;
|
|
16
19
|
/** PM position → USV content offset. */
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { scanDoc } from './encode.js';
|
|
2
2
|
/** Build the position index for `doc` (walks it once via the shared scanner). */
|
|
3
3
|
export function buildLineIndex(doc) {
|
|
4
|
-
|
|
4
|
+
return lineIndexOf(scanDoc(doc));
|
|
5
|
+
}
|
|
6
|
+
/** The index a scan's runs make. The commit path takes this one: it wants the
|
|
7
|
+
* content projection off the same walk (`encode.ts`, `scanContent`). */
|
|
8
|
+
export function lineIndexOf(s) {
|
|
5
9
|
const runs = s.runs;
|
|
6
10
|
let pmEndContent = 1;
|
|
7
11
|
let firstContentStart = 1;
|
|
@@ -24,3 +24,9 @@ export declare function contentEqual(a: Content, b: Content): boolean;
|
|
|
24
24
|
* on one side and a PM node attribute on the other.
|
|
25
25
|
*/
|
|
26
26
|
export declare function valueEqual(a: unknown, b: unknown): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* A value's canonical string: key-sorted at every depth, so two spellings of one value
|
|
29
|
+
* mint one key whatever order each side's producer wrote them in. What the key-minting
|
|
30
|
+
* half of the seam stringifies with, for the reason the equalities deep-compare.
|
|
31
|
+
*/
|
|
32
|
+
export declare function canonicalJson(value: unknown): string;
|
|
@@ -62,3 +62,22 @@ function deepEqual(a, b) {
|
|
|
62
62
|
}
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* A value's canonical string: key-sorted at every depth, so two spellings of one value
|
|
67
|
+
* mint one key whatever order each side's producer wrote them in. What the key-minting
|
|
68
|
+
* half of the seam stringifies with, for the reason the equalities deep-compare.
|
|
69
|
+
*/
|
|
70
|
+
export function canonicalJson(value) {
|
|
71
|
+
return JSON.stringify(canonical(value));
|
|
72
|
+
}
|
|
73
|
+
function canonical(v) {
|
|
74
|
+
if (Array.isArray(v))
|
|
75
|
+
return v.map(canonical);
|
|
76
|
+
if (typeof v !== 'object' || v === null)
|
|
77
|
+
return v;
|
|
78
|
+
const o = v;
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const k of Object.keys(o).sort())
|
|
81
|
+
out[k] = canonical(o[k]);
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { Schema } from 'prosemirror-model';
|
|
2
|
-
|
|
3
|
-
* Whether a link carrying `href` renders as one. A value with no scheme is relative
|
|
4
|
-
* to the embedding page and has none to refuse.
|
|
5
|
-
*
|
|
6
|
-
* A refused href is unchanged: it stays on the mark and round-trips, so a document
|
|
7
|
-
* survives an editor that declines to make it clickable.
|
|
8
|
-
*/
|
|
9
|
-
export declare function rendersHref(href: string): boolean;
|
|
2
|
+
import type { NodeType } from 'prosemirror-model';
|
|
10
3
|
/** The full field schema: every block kind, container, mark, and island. */
|
|
11
4
|
export declare const blockSchema: Schema<string, string>;
|
|
12
5
|
/** The constrained inline schema: one paragraph, no block splitting, no
|
|
@@ -28,3 +21,7 @@ export declare const plaintextSchema: Schema<string, never>;
|
|
|
28
21
|
export declare function hasMarks(schema: Schema): boolean;
|
|
29
22
|
/** True for the constrained inline schema (no block containers); decode branches on it. */
|
|
30
23
|
export declare function isInlineSchema(schema: Schema): boolean;
|
|
24
|
+
/** Whether `type` can hold a within-block line break, which a heading cannot (§`heading`).
|
|
25
|
+
* What a `\n` landing in a textblock is rewritten to turns on it (`breaks.ts`), and so
|
|
26
|
+
* does what decode puts between a block's continued segments. */
|
|
27
|
+
export declare function takesLineBreak(type: NodeType): boolean;
|