@quillmark/svelte 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/THEMING.md +48 -25
- package/dist/core/address.d.ts +42 -7
- package/dist/core/address.js +49 -21
- package/dist/core/bloom.d.ts +5 -5
- package/dist/core/bloom.js +8 -8
- package/dist/core/codec/anchor.d.ts +1 -1
- package/dist/core/codec/anchor.js +1 -1
- package/dist/core/codec/atoms.d.ts +10 -0
- package/dist/core/codec/atoms.js +78 -0
- package/dist/core/codec/clipboard.d.ts +59 -0
- package/dist/core/codec/clipboard.js +202 -0
- package/dist/core/codec/code.d.ts +1 -1
- package/dist/core/codec/code.js +4 -4
- package/dist/core/codec/decode.d.ts +1 -1
- package/dist/core/codec/decode.js +8 -8
- package/dist/core/codec/encode.d.ts +4 -4
- package/dist/core/codec/encode.js +14 -11
- package/dist/core/codec/field.d.ts +22 -27
- package/dist/core/codec/field.js +33 -32
- package/dist/core/codec/index.js +5 -8
- package/dist/core/codec/inputrules.js +56 -18
- package/dist/core/codec/islands.d.ts +11 -7
- package/dist/core/codec/islands.js +16 -10
- package/dist/core/codec/keymap.d.ts +5 -6
- package/dist/core/codec/keymap.js +8 -7
- package/dist/core/codec/lists.d.ts +3 -3
- package/dist/core/codec/lists.js +32 -13
- package/dist/core/codec/marks.d.ts +1 -1
- package/dist/core/codec/marks.js +1 -1
- package/dist/core/codec/positions.js +1 -1
- package/dist/core/codec/prose.css +266 -228
- package/dist/core/codec/schema.js +4 -4
- package/dist/core/codec/slash.d.ts +39 -41
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +12 -16
- package/dist/core/codec/table-view.js +488 -292
- package/dist/core/codec/table.d.ts +42 -15
- package/dist/core/codec/table.js +73 -27
- package/dist/core/errors.d.ts +17 -7
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +6 -5
- package/dist/core/lifecycle.d.ts +21 -5
- package/dist/core/lifecycle.js +34 -8
- package/dist/core/rebind.svelte.d.ts +18 -4
- package/dist/core/rebind.svelte.js +30 -12
- package/dist/core/teardown.d.ts +1 -1
- package/dist/core/teardown.js +1 -1
- package/dist/core/theme.css +173 -58
- package/dist/preset/index.css +1 -1
- package/dist/preset/recipes.css +79 -30
- package/dist/preset/scale.css +40 -28
- package/dist/preview/Preview.svelte +22 -19
- package/dist/preview/Preview.svelte.d.ts +8 -8
- package/dist/preview/bridge.d.ts +6 -4
- package/dist/preview/bridge.js +19 -9
- package/dist/preview/controller.d.ts +13 -6
- package/dist/preview/controller.js +8 -5
- package/dist/preview/geometry.d.ts +17 -2
- package/dist/preview/geometry.js +21 -1
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/index.js +9 -2
- package/dist/preview/overlay.d.ts +2 -1
- package/dist/preview/overlay.js +26 -11
- package/dist/preview/paint.js +19 -17
- package/dist/visual/ArrayField.svelte +103 -83
- package/dist/visual/ArrayField.svelte.d.ts +5 -4
- package/dist/visual/BooleanField.svelte +8 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -2
- package/dist/visual/Card.svelte +161 -333
- package/dist/visual/Card.svelte.d.ts +5 -4
- package/dist/visual/CardControls.svelte +1 -1
- package/dist/visual/DateField.svelte +19 -19
- package/dist/visual/DateField.svelte.d.ts +3 -3
- package/dist/visual/DiagnosticList.svelte +2 -2
- package/dist/visual/EnumField.svelte +48 -28
- package/dist/visual/EnumField.svelte.d.ts +5 -4
- package/dist/visual/Field.svelte +207 -132
- package/dist/visual/Field.svelte.d.ts +11 -7
- package/dist/visual/FieldHint.svelte +79 -48
- package/dist/visual/FieldHint.svelte.d.ts +1 -1
- package/dist/visual/FieldLabel.svelte +8 -8
- package/dist/visual/FormatPopover.svelte +82 -35
- package/dist/visual/NumberField.svelte +5 -5
- package/dist/visual/NumberField.svelte.d.ts +1 -1
- package/dist/visual/ObjectField.svelte +28 -9
- package/dist/visual/ObjectField.svelte.d.ts +4 -2
- package/dist/visual/ProseArrayElement.svelte +12 -19
- package/dist/visual/ProseArrayElement.svelte.d.ts +1 -2
- package/dist/visual/ProseField.svelte +62 -85
- package/dist/visual/ProseField.svelte.d.ts +14 -17
- package/dist/visual/SlashMenu.svelte +42 -16
- package/dist/visual/TextField.svelte +5 -5
- package/dist/visual/TextField.svelte.d.ts +1 -1
- package/dist/visual/TipsCard.svelte +43 -28
- package/dist/visual/VisualEditor.svelte +18 -19
- package/dist/visual/VisualEditor.svelte.d.ts +2 -3
- package/dist/visual/VisualEditorInner.svelte +251 -184
- package/dist/visual/VisualEditorInner.svelte.d.ts +2 -3
- package/dist/visual/controls.css +85 -38
- package/dist/visual/diagnostics.d.ts +13 -21
- package/dist/visual/diagnostics.js +7 -19
- package/dist/visual/domid.js +3 -3
- package/dist/visual/ext.d.ts +1 -1
- package/dist/visual/ext.js +1 -1
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/index.d.ts +1 -0
- package/dist/visual/index.js +10 -3
- package/dist/visual/leaves.d.ts +47 -0
- package/dist/visual/leaves.js +31 -0
- package/dist/visual/links.d.ts +24 -0
- package/dist/visual/links.js +67 -0
- package/dist/visual/props.d.ts +33 -12
- package/dist/visual/signals.d.ts +6 -6
- package/dist/visual/strings.d.ts +14 -19
- package/dist/visual/strings.js +5 -7
- package/dist/visual/structure.d.ts +24 -17
- package/dist/visual/structure.js +8 -8
- package/dist/visual/synced.svelte.d.ts +1 -1
- package/dist/visual/synced.svelte.js +4 -4
- package/dist/visual/tips.d.ts +4 -2
- package/dist/visual/tips.js +7 -4
- package/package.json +2 -2
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DOMParser as PMDOMParser, Slice } from 'prosemirror-model';
|
|
2
|
+
import type { Node as PMNode, Schema } from 'prosemirror-model';
|
|
3
|
+
import { Plugin } from 'prosemirror-state';
|
|
4
|
+
import type { TableCell, TableProps } from '@quillmark/wasm';
|
|
5
|
+
import { type TableAlign } from './table.js';
|
|
6
|
+
/** What a clipboard event hands over, which is all either arm needs of it: jsdom has no
|
|
7
|
+
* `DataTransfer`, and the suite drives these paths with the two methods themselves. */
|
|
8
|
+
interface Clipboard {
|
|
9
|
+
getData: (type: string) => string;
|
|
10
|
+
setData: (type: string, data: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A `<table>` element as props. `rowspan` is not carried — a merged cell is not a
|
|
14
|
+
* rectangle, so a table with one arrives with the rows below it short and
|
|
15
|
+
* {@link normalizeTable} pads them at the end. `colspan` is, as the empty cells it
|
|
16
|
+
* covers, so the columns after it keep their index.
|
|
17
|
+
*
|
|
18
|
+
* Only the table's own rows: a `<tr>` inside a nested table belongs to a cell this
|
|
19
|
+
* reader is about to flatten, not to a rank of this one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function tableFromDOM(dom: Element): TableProps;
|
|
22
|
+
/** The table a paste is carrying, or `undefined` for a paste that holds none. The first
|
|
23
|
+
* one: a clipboard holding a table among other blocks is a document paste, and only a
|
|
24
|
+
* surface that takes nothing but a table (a cell) reads it this way. */
|
|
25
|
+
export declare function tableFromClipboard(data: Clipboard | null | undefined): TableProps | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* A rectangle as a `<table>`: the wire the HTML arm writes and reads. Its first row is
|
|
28
|
+
* a `<th>` row whatever row of the source it came from, that being the row index 0 a
|
|
29
|
+
* paste lands it at.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tableToDOM(block: TableCell[][], aligns: TableAlign[], owner: Document): Element;
|
|
32
|
+
/**
|
|
33
|
+
* A rectangle as pipe rows, delimiter row included: what a plain-text target reads as a
|
|
34
|
+
* table and what markdown carries. The delimiter follows the first row for the reason
|
|
35
|
+
* that row serializes as `<th>` — a pasted rectangle's row 0 is a header.
|
|
36
|
+
*/
|
|
37
|
+
export declare function tableToPipe(block: TableCell[][], aligns: TableAlign[]): string;
|
|
38
|
+
/** Put a rectangle on the clipboard, both arms. The caller is a `copy` / `cut` handler
|
|
39
|
+
* over a selection its own surface holds, so the rectangle arrives already cut out and
|
|
40
|
+
* claiming the event is the caller's. */
|
|
41
|
+
export declare function writeRectangle(data: Clipboard, block: TableCell[][], aligns: TableAlign[], owner: Document): void;
|
|
42
|
+
/** The clipboard's parser: the schema's own rules with the table rule ahead of them.
|
|
43
|
+
* Ahead, because a `<table>` otherwise matches nothing and its cells' text falls
|
|
44
|
+
* through to the enclosing block. */
|
|
45
|
+
export declare function tableClipboardParser(schema: Schema): PMDOMParser;
|
|
46
|
+
/** Mint an id for every island a paste is carrying. An id is part of the document's
|
|
47
|
+
* canonical bytes and the channel that addresses an island (CODEC §Islands), so a
|
|
48
|
+
* pasted island with none reaches the store as an insert nothing can name afterwards.
|
|
49
|
+
* One minter for the whole slice, or a paste placing two tables hands them one id. */
|
|
50
|
+
export declare function stampIslandIds(slice: Slice, doc: PMNode): Slice;
|
|
51
|
+
/**
|
|
52
|
+
* The block leaf's clipboard plugin: the table rule on the way in, and the id mint over
|
|
53
|
+
* what it produced. A `clipboardParser` rather than the schema's own `parseDOM`,
|
|
54
|
+
* because what a `<table>` in the DOM means is a question only the clipboard asks — the
|
|
55
|
+
* schema's parse rules are also what a re-parse of the editor's own DOM runs through,
|
|
56
|
+
* where an island is already a node and a `<table>` is the chrome drawing one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function tableClipboardPlugin(schema: Schema): Plugin;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// The table island's clipboard door, both directions: what a copy writes, a paste
|
|
2
|
+
// reads. The design and the two wire formats are CODEC §"The table island" and
|
|
3
|
+
// §"Markdown at the edges"; what is here is the grammar.
|
|
4
|
+
//
|
|
5
|
+
// What crosses is A rectangle, never a cell. A `<table>` read in is one; the held cell
|
|
6
|
+
// selection written out is one; a rectangle overlaid onto a table at a cell is one. The
|
|
7
|
+
// wire therefore carries `TableCell[][]` and no coordinates: each end names its own
|
|
8
|
+
// corner.
|
|
9
|
+
//
|
|
10
|
+
// A row 0 is A header, on both sides, which is the model's own rule (`table.ts`
|
|
11
|
+
// §`allRows`): whichever row holds index 0 is the header. So a `<table>` with no `<th>`
|
|
12
|
+
// promotes its first row, and a copied rectangle's first row is the header of what it
|
|
13
|
+
// pastes.
|
|
14
|
+
import { DOMParser as PMDOMParser, DOMSerializer, Fragment, Slice } from 'prosemirror-model';
|
|
15
|
+
import { Plugin } from 'prosemirror-state';
|
|
16
|
+
import { islandMinter } from './islands.js';
|
|
17
|
+
import { inlineSchema } from './schema.js';
|
|
18
|
+
import { ALIGNS, cellContent, cellFromDoc, emptyCell, normalizeTable } from './table.js';
|
|
19
|
+
import { decode } from './decode.js';
|
|
20
|
+
/** The two keys, spelled once: a reader and a writer that disagreed about which holds
|
|
21
|
+
* the table would each work alone and never round-trip. */
|
|
22
|
+
const HTML = 'text/html';
|
|
23
|
+
const PLAIN = 'text/plain';
|
|
24
|
+
// ── Reading a table in ──────────────────────────────────────────────────────
|
|
25
|
+
/**
|
|
26
|
+
* One cell from its `<td>` / `<th>`. The inline schema is the whole normalization: it
|
|
27
|
+
* declares one paragraph, no containers and no islands, so a cell holding blocks
|
|
28
|
+
* flattens to its text and an image in one drops out rather than leaving a slot char
|
|
29
|
+
* behind. Whitespace collapses and the ends trim, which is PM's own parse.
|
|
30
|
+
*/
|
|
31
|
+
function cellFromDOM(dom) {
|
|
32
|
+
const para = PMDOMParser.fromSchema(inlineSchema).parse(dom, {
|
|
33
|
+
topNode: inlineSchema.nodes.paragraph.create()
|
|
34
|
+
});
|
|
35
|
+
return cellFromDoc(inlineSchema.nodes.doc.create(null, para), emptyCell());
|
|
36
|
+
}
|
|
37
|
+
/** A column's alignment off its header cell, which is where a pipe table's delimiter
|
|
38
|
+
* row puts it. Anything else (`start`, `justify`, absent) is `none`. */
|
|
39
|
+
function alignFromDOM(dom) {
|
|
40
|
+
const raw = (dom.getAttribute('align') ??
|
|
41
|
+
dom.style?.textAlign ??
|
|
42
|
+
'').toLowerCase();
|
|
43
|
+
return ALIGNS.includes(raw) ? raw : 'none';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A `<table>` element as props. `rowspan` is not carried — a merged cell is not a
|
|
47
|
+
* rectangle, so a table with one arrives with the rows below it short and
|
|
48
|
+
* {@link normalizeTable} pads them at the end. `colspan` is, as the empty cells it
|
|
49
|
+
* covers, so the columns after it keep their index.
|
|
50
|
+
*
|
|
51
|
+
* Only the table's own rows: a `<tr>` inside a nested table belongs to a cell this
|
|
52
|
+
* reader is about to flatten, not to a rank of this one.
|
|
53
|
+
*/
|
|
54
|
+
export function tableFromDOM(dom) {
|
|
55
|
+
const rows = [];
|
|
56
|
+
const aligns = [];
|
|
57
|
+
for (const tr of dom.querySelectorAll('tr')) {
|
|
58
|
+
if (tr.closest('table') !== dom)
|
|
59
|
+
continue;
|
|
60
|
+
const cells = [];
|
|
61
|
+
for (const box of tr.children) {
|
|
62
|
+
if (box.tagName !== 'TD' && box.tagName !== 'TH')
|
|
63
|
+
continue;
|
|
64
|
+
if (!rows.length)
|
|
65
|
+
aligns.push(alignFromDOM(box));
|
|
66
|
+
cells.push(cellFromDOM(box));
|
|
67
|
+
const span = Number(box.getAttribute('colspan')) || 1;
|
|
68
|
+
for (let i = 1; i < span; i++) {
|
|
69
|
+
if (!rows.length)
|
|
70
|
+
aligns.push('none');
|
|
71
|
+
cells.push(emptyCell());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
rows.push(cells);
|
|
75
|
+
}
|
|
76
|
+
const [header = [], ...body] = rows;
|
|
77
|
+
return normalizeTable({ header, rows: body, aligns });
|
|
78
|
+
}
|
|
79
|
+
/** The table a paste is carrying, or `undefined` for a paste that holds none. The first
|
|
80
|
+
* one: a clipboard holding a table among other blocks is a document paste, and only a
|
|
81
|
+
* surface that takes nothing but a table (a cell) reads it this way. */
|
|
82
|
+
export function tableFromClipboard(data) {
|
|
83
|
+
const html = data?.getData(HTML);
|
|
84
|
+
if (!html || !/<table[\s>]/i.test(html))
|
|
85
|
+
return undefined;
|
|
86
|
+
const dom = new DOMParser().parseFromString(html, HTML).querySelector('table');
|
|
87
|
+
return dom ? tableFromDOM(dom) : undefined;
|
|
88
|
+
}
|
|
89
|
+
// ── Writing a rectangle out ─────────────────────────────────────────────────
|
|
90
|
+
/** A cell's inline DOM, serialized by the schema that declares its marks, which is what
|
|
91
|
+
* makes {@link cellFromDOM} its exact inverse. */
|
|
92
|
+
function cellToDOM(cell, owner) {
|
|
93
|
+
const doc = decode(cellContent(cell), inlineSchema);
|
|
94
|
+
const line = doc.firstChild;
|
|
95
|
+
return DOMSerializer.fromSchema(inlineSchema).serializeFragment(line ? line.content : Fragment.empty, { document: owner });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A rectangle as a `<table>`: the wire the HTML arm writes and reads. Its first row is
|
|
99
|
+
* a `<th>` row whatever row of the source it came from, that being the row index 0 a
|
|
100
|
+
* paste lands it at.
|
|
101
|
+
*/
|
|
102
|
+
export function tableToDOM(block, aligns, owner) {
|
|
103
|
+
const table = owner.createElement('table');
|
|
104
|
+
block.forEach((cells, r) => {
|
|
105
|
+
const tr = owner.createElement('tr');
|
|
106
|
+
cells.forEach((cell, c) => {
|
|
107
|
+
const box = owner.createElement(r === 0 ? 'th' : 'td');
|
|
108
|
+
const align = aligns[c] ?? 'none';
|
|
109
|
+
if (align !== 'none')
|
|
110
|
+
box.style.textAlign = align;
|
|
111
|
+
box.appendChild(cellToDOM(cell, owner));
|
|
112
|
+
tr.appendChild(box);
|
|
113
|
+
});
|
|
114
|
+
table.appendChild(tr);
|
|
115
|
+
});
|
|
116
|
+
return table;
|
|
117
|
+
}
|
|
118
|
+
/** A cell's text as one pipe-table field: the delimiter escaped, since a `|` inside a
|
|
119
|
+
* cell would otherwise open a column that is not there. Marks are dropped, markdown
|
|
120
|
+
* being the lossy arm. */
|
|
121
|
+
const pipeCell = (cell) => cell.text.replaceAll('|', '\\|');
|
|
122
|
+
/**
|
|
123
|
+
* A rectangle as pipe rows, delimiter row included: what a plain-text target reads as a
|
|
124
|
+
* table and what markdown carries. The delimiter follows the first row for the reason
|
|
125
|
+
* that row serializes as `<th>` — a pasted rectangle's row 0 is a header.
|
|
126
|
+
*/
|
|
127
|
+
export function tableToPipe(block, aligns) {
|
|
128
|
+
const width = block[0]?.length ?? 0;
|
|
129
|
+
const rule = Array.from({ length: width }, (_, c) => {
|
|
130
|
+
const align = aligns[c] ?? 'none';
|
|
131
|
+
if (align === 'center')
|
|
132
|
+
return ':---:';
|
|
133
|
+
return align === 'left' ? ':---' : align === 'right' ? '---:' : '---';
|
|
134
|
+
});
|
|
135
|
+
const line = (cells) => `| ${cells.join(' | ')} |`;
|
|
136
|
+
const [head = [], ...body] = block;
|
|
137
|
+
return [line(head.map(pipeCell)), line(rule), ...body.map((row) => line(row.map(pipeCell)))].join('\n');
|
|
138
|
+
}
|
|
139
|
+
/** Put a rectangle on the clipboard, both arms. The caller is a `copy` / `cut` handler
|
|
140
|
+
* over a selection its own surface holds, so the rectangle arrives already cut out and
|
|
141
|
+
* claiming the event is the caller's. */
|
|
142
|
+
export function writeRectangle(data, block, aligns, owner) {
|
|
143
|
+
data.setData(HTML, tableToDOM(block, aligns, owner).outerHTML);
|
|
144
|
+
data.setData(PLAIN, tableToPipe(block, aligns));
|
|
145
|
+
}
|
|
146
|
+
// ── The paste door ──────────────────────────────────────────────────────────
|
|
147
|
+
/** The `<table>` rule: a clipboard table becomes the island the model already holds
|
|
148
|
+
* rather than the paragraph its text flattens to. `id` is left empty for
|
|
149
|
+
* {@link stampIslandIds}, a parse rule having no document to mint against. `loss` is
|
|
150
|
+
* authored: what this reader produces is a pipe table, so markdown carries it whole. */
|
|
151
|
+
function tableParseRule() {
|
|
152
|
+
return {
|
|
153
|
+
tag: 'table',
|
|
154
|
+
node: 'island_block',
|
|
155
|
+
getAttrs: (dom) => ({
|
|
156
|
+
id: '',
|
|
157
|
+
islandType: 'table',
|
|
158
|
+
props: tableFromDOM(dom),
|
|
159
|
+
loss: 'lossless'
|
|
160
|
+
})
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** The clipboard's parser: the schema's own rules with the table rule ahead of them.
|
|
164
|
+
* Ahead, because a `<table>` otherwise matches nothing and its cells' text falls
|
|
165
|
+
* through to the enclosing block. */
|
|
166
|
+
export function tableClipboardParser(schema) {
|
|
167
|
+
return new PMDOMParser(schema, [tableParseRule(), ...PMDOMParser.fromSchema(schema).rules]);
|
|
168
|
+
}
|
|
169
|
+
/** An island node a parse rule produced, which is the only kind carrying no id. */
|
|
170
|
+
const unstamped = (node) => (node.type.name === 'island_block' || node.type.name === 'island_inline') && !node.attrs.id;
|
|
171
|
+
/** Mint an id for every island a paste is carrying. An id is part of the document's
|
|
172
|
+
* canonical bytes and the channel that addresses an island (CODEC §Islands), so a
|
|
173
|
+
* pasted island with none reaches the store as an insert nothing can name afterwards.
|
|
174
|
+
* One minter for the whole slice, or a paste placing two tables hands them one id. */
|
|
175
|
+
export function stampIslandIds(slice, doc) {
|
|
176
|
+
const mint = islandMinter(doc);
|
|
177
|
+
const walk = (fragment) => {
|
|
178
|
+
const out = [];
|
|
179
|
+
fragment.forEach((node) => {
|
|
180
|
+
const next = node.content.size ? node.copy(walk(node.content)) : node;
|
|
181
|
+
out.push(unstamped(next) ? next.type.create({ ...next.attrs, id: mint() }, next.content) : next);
|
|
182
|
+
});
|
|
183
|
+
return Fragment.fromArray(out);
|
|
184
|
+
};
|
|
185
|
+
return new Slice(walk(slice.content), slice.openStart, slice.openEnd);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The block leaf's clipboard plugin: the table rule on the way in, and the id mint over
|
|
189
|
+
* what it produced. A `clipboardParser` rather than the schema's own `parseDOM`,
|
|
190
|
+
* because what a `<table>` in the DOM means is a question only the clipboard asks — the
|
|
191
|
+
* schema's parse rules are also what a re-parse of the editor's own DOM runs through,
|
|
192
|
+
* where an island is already a node and a `<table>` is the chrome drawing one.
|
|
193
|
+
*/
|
|
194
|
+
export function tableClipboardPlugin(schema) {
|
|
195
|
+
const parser = tableClipboardParser(schema);
|
|
196
|
+
return new Plugin({
|
|
197
|
+
props: {
|
|
198
|
+
clipboardParser: parser,
|
|
199
|
+
transformPasted: (slice, view) => stampIslandIds(slice, view.state.doc)
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -6,7 +6,7 @@ import { type Command } from 'prosemirror-state';
|
|
|
6
6
|
*
|
|
7
7
|
* `Enter` is upstream's `newlineInCode`, and it is load-bearing rather than
|
|
8
8
|
* cosmetic: inside a `list_item > code_block` the list link's `splitListItem`
|
|
9
|
-
* otherwise splits the
|
|
9
|
+
* otherwise splits the item in two, each half holding a code block. Every command
|
|
10
10
|
* here declines outside a code block, so the list links keep the keys elsewhere.
|
|
11
11
|
*/
|
|
12
12
|
export declare function codeKeymap(schema: Schema): Record<string, Command>;
|
package/dist/core/codec/code.js
CHANGED
|
@@ -16,7 +16,7 @@ function codeBlockAt(state) {
|
|
|
16
16
|
/**
|
|
17
17
|
* Start offsets of the lines the range `[f, t)` covers, in document order.
|
|
18
18
|
*
|
|
19
|
-
* A line whose start
|
|
19
|
+
* A line whose start is `t` is excluded on a non-empty range: a selection ending at
|
|
20
20
|
* a line start does not reach into that line, the editor convention (else selecting
|
|
21
21
|
* whole lines by dragging to the next line's start indents one line too many).
|
|
22
22
|
*/
|
|
@@ -44,7 +44,7 @@ function outdentWidth(text, s) {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Tab in a code block: insert one indent unit, or indent every covered line when the
|
|
47
|
-
* selection spans a newline. A single-line selection is
|
|
47
|
+
* selection spans a newline. A single-line selection is replaced by the unit: Tab
|
|
48
48
|
* types, the way any other key does.
|
|
49
49
|
*/
|
|
50
50
|
const indentInCode = (state, dispatch) => {
|
|
@@ -75,7 +75,7 @@ const indentInCode = (state, dispatch) => {
|
|
|
75
75
|
/**
|
|
76
76
|
* Shift-Tab in a code block: remove one indent level from every covered line.
|
|
77
77
|
*
|
|
78
|
-
* Declines when no covered line carries an indent, so the key is
|
|
78
|
+
* Declines when no covered line carries an indent, so the key is not swallowed: it
|
|
79
79
|
* falls through to the list link and then out of the leaf, which is the body's
|
|
80
80
|
* keyboard exit from inside a code block (VISUAL_EDITOR §Chrome).
|
|
81
81
|
*/
|
|
@@ -104,7 +104,7 @@ const outdentInCode = (state, dispatch) => {
|
|
|
104
104
|
*
|
|
105
105
|
* `Enter` is upstream's `newlineInCode`, and it is load-bearing rather than
|
|
106
106
|
* cosmetic: inside a `list_item > code_block` the list link's `splitListItem`
|
|
107
|
-
* otherwise splits the
|
|
107
|
+
* otherwise splits the item in two, each half holding a code block. Every command
|
|
108
108
|
* here declines outside a code block, so the list links keep the keys elsewhere.
|
|
109
109
|
*/
|
|
110
110
|
export function codeKeymap(schema) {
|
|
@@ -9,7 +9,7 @@ interface DecodeOpts {
|
|
|
9
9
|
plaintext?: boolean;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* A `Content` as
|
|
12
|
+
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`.
|
|
13
13
|
* The rendering half of the codec's job with no editing attached (no PM view, no
|
|
14
14
|
* plugins, no `contenteditable`) for chrome that must show content in the same
|
|
15
15
|
* mark vocabulary a leaf edits it in (the tips card). A second renderer over the
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// prefix (`list_item`/`quote` → lists/blockquote), select the block node by
|
|
5
5
|
// `kind`, apply marks over their `[start,end)` USV ranges (PM splits inline nodes
|
|
6
6
|
// at mark boundaries), and lower island slots to leaf nodes (inline in a `para`
|
|
7
|
-
// line, block on an `island` line). Anchors are
|
|
7
|
+
// line, block on an `island` line). Anchors are not applied here; they are
|
|
8
8
|
// decorations (field.ts). Positions throughout are USV; `Array.from` iterates by
|
|
9
9
|
// code point so an astral char is one unit, never a surrogate half.
|
|
10
10
|
import { DOMSerializer } from 'prosemirror-model';
|
|
@@ -24,7 +24,7 @@ export function usvLength(s) {
|
|
|
24
24
|
return n;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* A `Content` as
|
|
27
|
+
* A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`.
|
|
28
28
|
* The rendering half of the codec's job with no editing attached (no PM view, no
|
|
29
29
|
* plugins, no `contenteditable`) for chrome that must show content in the same
|
|
30
30
|
* mark vocabulary a leaf edits it in (the tips card). A second renderer over the
|
|
@@ -92,7 +92,7 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
92
92
|
continue;
|
|
93
93
|
}
|
|
94
94
|
const here = path[depth];
|
|
95
|
-
// `ContentContainer` is an
|
|
95
|
+
// `ContentContainer` is an open set: a bare `here.container === 'x'` does not
|
|
96
96
|
// narrow, so the two arms this schema builds are claimed by the boundary's
|
|
97
97
|
// guard and a literal check, and everything else is an unknown the inert
|
|
98
98
|
// wrapper carries (CODEC §Open sets); no branch degrades silently.
|
|
@@ -106,7 +106,7 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
106
106
|
const c = atDepth(leaves[j], depth);
|
|
107
107
|
if (!c || !isListItemContainer(c) || c.ordered !== ordered || c.start !== start)
|
|
108
108
|
break;
|
|
109
|
-
// An ordinal reset is an
|
|
109
|
+
// An ordinal reset is an adjacent sibling list (content preserves the
|
|
110
110
|
// shape): merging it here would re-encode `[0,1,0]` as `[0,1,2]`,
|
|
111
111
|
// breaking the decode round-trip on the first edit.
|
|
112
112
|
if (c.ordinal < prevOrd)
|
|
@@ -131,7 +131,7 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
|
131
131
|
continue;
|
|
132
132
|
}
|
|
133
133
|
// quote and every unknown container share one shape: a wrapper over the run
|
|
134
|
-
// of leaves carrying the
|
|
134
|
+
// of leaves carrying the identical container here (identity by `containerKey`,
|
|
135
135
|
// so two adjacent unknowns differing only in `attrs` stay two wrappers).
|
|
136
136
|
const key = containerKey(here);
|
|
137
137
|
let j = i + 1;
|
|
@@ -165,7 +165,7 @@ function containerKey(c) {
|
|
|
165
165
|
return 'attrs' in c ? `${c.container}\u0000${JSON.stringify(c.attrs)}` : c.container;
|
|
166
166
|
}
|
|
167
167
|
/** A single leaf block node (para/heading/code/rule/island, or an unknown kind
|
|
168
|
-
* carried on a paragraph) from its segments. `kind` is an
|
|
168
|
+
* carried on a paragraph) from its segments. `kind` is an open set, so the two
|
|
169
169
|
* payload-carrying arms read through the boundary's guards; the payload-free arms
|
|
170
170
|
* compare literally, and anything left is unknown. */
|
|
171
171
|
function makeLeaf(schema, leaf, marks, cursor) {
|
|
@@ -191,13 +191,13 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
191
191
|
if (isHeadingLine(line)) {
|
|
192
192
|
return schema.nodes.heading.create({ level: line.level }, inline);
|
|
193
193
|
}
|
|
194
|
-
// An unknown kind renders as a paragraph
|
|
194
|
+
// An unknown kind renders as a paragraph and rides on it, so it re-encodes
|
|
195
195
|
// verbatim rather than flattening to `para` on the field's first edit.
|
|
196
196
|
const unknown = line.kind === 'para' ? null : { kind: line.kind, attrs: 'attrs' in line ? line.attrs : null };
|
|
197
197
|
return schema.nodes.paragraph.create({ unknown }, inline);
|
|
198
198
|
}
|
|
199
199
|
/** Consume the next island entry as PM node attrs (text-order matched to slots):
|
|
200
|
-
* the
|
|
200
|
+
* the whole entry, `loss` included, which an island edit has to write back
|
|
201
201
|
* (`islands.ts`). A slot with no entry behind it is a malformed content; the empty
|
|
202
202
|
* attrs keep the decode total rather than throwing on a read. */
|
|
203
203
|
function islandAttrs(cursor) {
|
|
@@ -27,7 +27,7 @@ export interface Scan {
|
|
|
27
27
|
usvEnd: number;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Walk a PM document once, producing the content projection
|
|
30
|
+
* Walk a PM document once, producing the content projection and the position-map
|
|
31
31
|
* runs (they share the traversal so they can never disagree). This is the single
|
|
32
32
|
* source both `pmToContent` and the position map draw from.
|
|
33
33
|
*/
|
|
@@ -40,14 +40,14 @@ interface LowerOpts {
|
|
|
40
40
|
id: string;
|
|
41
41
|
pos: number;
|
|
42
42
|
}[];
|
|
43
|
-
/** Anchor decoration positions after the edit, in
|
|
43
|
+
/** Anchor decoration positions after the edit, in final (new) USV coords. */
|
|
44
44
|
newAnchors?: {
|
|
45
45
|
id: string;
|
|
46
46
|
pos: number;
|
|
47
47
|
}[];
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* One old→new content edit with the text splice it implies.
|
|
50
|
+
* One old→new content edit with the text splice it implies. Both sides are content,
|
|
51
51
|
* never a PM doc: the projection of the new doc (`pmToContent`) is the caller's to
|
|
52
52
|
* hold, because a doc-taking overload re-walks the whole tree, once per keystroke,
|
|
53
53
|
* for a value the caller has in hand.
|
|
@@ -56,7 +56,7 @@ interface LowerOpts {
|
|
|
56
56
|
* and the commit path holds the edit across the lowering. `contentEdit` being its
|
|
57
57
|
* only constructor is what keeps a `delta` from disagreeing with the contents it
|
|
58
58
|
* was diffed from; `lower` is where a slot inside that splice moves to the island
|
|
59
|
-
* channel, so this `delta` is the
|
|
59
|
+
* channel, so this `delta` is the raw splice, not always the one committed.
|
|
60
60
|
*/
|
|
61
61
|
export interface ContentEdit {
|
|
62
62
|
readonly oldRt: Content;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAnchorMark } from '@quillmark/wasm';
|
|
2
|
+
import { core } from '../lifecycle.js';
|
|
2
3
|
import { codePoints, usvLength } from './decode.js';
|
|
3
4
|
import { ISLAND_SLOT, islandEntryFromNode } from './islands.js';
|
|
4
5
|
import { contentDescriptorFromPM, descriptorOf, markKey } from './marks.js';
|
|
@@ -9,7 +10,7 @@ function appendText(acc, s) {
|
|
|
9
10
|
acc.usvEnd += usvLength(s);
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
|
-
* Walk a PM document once, producing the content projection
|
|
13
|
+
* Walk a PM document once, producing the content projection and the position-map
|
|
13
14
|
* runs (they share the traversal so they can never disagree). This is the single
|
|
14
15
|
* source both `pmToContent` and the position map draw from.
|
|
15
16
|
*/
|
|
@@ -49,7 +50,7 @@ function scanBlock(acc, node, nodePos, containers) {
|
|
|
49
50
|
case 'paragraph':
|
|
50
51
|
case 'heading': {
|
|
51
52
|
// One kind value per block: the opening line and its hard-break
|
|
52
|
-
// continuations read the
|
|
53
|
+
// continuations read the same object, so they cannot disagree.
|
|
53
54
|
const kind = textblockKind(node);
|
|
54
55
|
beginLine(acc, contentStart, containers, kind);
|
|
55
56
|
scanInline(acc, node, contentStart, containers, kind);
|
|
@@ -255,7 +256,7 @@ function diffLines(oldRt, newRt) {
|
|
|
255
256
|
}
|
|
256
257
|
/** A line minus its `containers` / `continues` envelope: exactly `setKind`'s
|
|
257
258
|
* payload, which the boundary names `ContentLineKind`. Lifting it whole is what
|
|
258
|
-
* keeps this arm-agnostic: `kind` is an
|
|
259
|
+
* keeps this arm-agnostic: `kind` is an open set, so an arm-by-arm switch would
|
|
259
260
|
* have to guess at the unknown arm's payload and would drift on every arm
|
|
260
261
|
* upstream adds. */
|
|
261
262
|
function kindPart(l) {
|
|
@@ -285,14 +286,14 @@ function kindKey(l) {
|
|
|
285
286
|
}
|
|
286
287
|
/**
|
|
287
288
|
* Split the edit's islands off the text splice. `applyChange` throws
|
|
288
|
-
* `IslandSlotInInsert` on a `delta` that types a slot, so a slot
|
|
289
|
+
* `IslandSlotInInsert` on a `delta` that types a slot, so a slot never rides the
|
|
289
290
|
* delta: every slot inside the splice's inserted region is stripped from it and
|
|
290
291
|
* placed by `{ op: 'insert', at }`, which carries the backing entry in the same
|
|
291
292
|
* op. Every other island kept its slot, so a changed payload is `{ op: 'set' }`,
|
|
292
|
-
* addressed by the id both sides carry. A
|
|
293
|
+
* addressed by the id both sides carry. A deleted island needs no op at all: the
|
|
293
294
|
* delta that removes its slot drops the entry.
|
|
294
295
|
*
|
|
295
|
-
* `at` is the slot's position in the
|
|
296
|
+
* `at` is the slot's position in the final text: inserts apply left to right, so
|
|
296
297
|
* by the time the k-th lands, the k slots before it are back in place.
|
|
297
298
|
*
|
|
298
299
|
* The pairing is positional for `insert` and by id for `set`, which is what each
|
|
@@ -337,7 +338,7 @@ function slotPositions(text) {
|
|
|
337
338
|
}
|
|
338
339
|
return out;
|
|
339
340
|
}
|
|
340
|
-
/** The splice's inserted region `[start, end)` in
|
|
341
|
+
/** The splice's inserted region `[start, end)` in final coords. `diffText` emits
|
|
341
342
|
* one `[retain?][delete?][insert?][retain?]`, so the leading retain is where the
|
|
342
343
|
* insert lands and its length is how far it reaches. */
|
|
343
344
|
function insertedRegion(delta) {
|
|
@@ -349,7 +350,7 @@ function insertedRegion(delta) {
|
|
|
349
350
|
return { start, end: start + (insert ? usvLength(insert.insert) : 0) };
|
|
350
351
|
}
|
|
351
352
|
/** The delta with every island slot removed from its insert, or `undefined` when
|
|
352
|
-
* that leaves nothing but retains. Retain/delete arithmetic reads the
|
|
353
|
+
* that leaves nothing but retains. Retain/delete arithmetic reads the old text,
|
|
353
354
|
* so dropping inserted characters leaves the rest of the splice intact. */
|
|
354
355
|
function withoutSlots(delta) {
|
|
355
356
|
const ops = [];
|
|
@@ -374,7 +375,7 @@ function withoutSlots(delta) {
|
|
|
374
375
|
* A post-delta position carried through the bundle's island inserts, which move
|
|
375
376
|
* text exactly as the delta does and rebase marks by the same rule (start-assoc
|
|
376
377
|
* `after`, end-assoc `before`; an anchor is a point and holds `before`). Ascending
|
|
377
|
-
* `at`s apply left to right, so each comparison reads the
|
|
378
|
+
* `at`s apply left to right, so each comparison reads the running position.
|
|
378
379
|
*/
|
|
379
380
|
function shiftPastIslands(pos, insertedAt, assoc) {
|
|
380
381
|
let p = pos;
|
|
@@ -386,7 +387,9 @@ function shiftPastIslands(pos, insertedAt, assoc) {
|
|
|
386
387
|
/** Formatting/unknown marks → add/remove ops; anchors → add/removeAnchor by id. */
|
|
387
388
|
function diffMarks(oldRt, newRt, delta, insertedAt, opts) {
|
|
388
389
|
const ops = [];
|
|
389
|
-
//
|
|
390
|
+
// Read the gate once per diff rather than once per mark.
|
|
391
|
+
const { mapPos } = core();
|
|
392
|
+
// Rebase old marks through both text-moving channels to final coords, exactly
|
|
390
393
|
// as `applyChange` does internally (start assoc `after`, end assoc `before`).
|
|
391
394
|
const rebase = (pos, assoc) => shiftPastIslands(delta ? mapPos(delta, pos, assoc) : pos, insertedAt, assoc);
|
|
392
395
|
// Group both sides by descriptor key (excluding anchors).
|
|
@@ -4,7 +4,7 @@ import { EditorView } from 'prosemirror-view';
|
|
|
4
4
|
import type { Document, Content, Addr, Quill } from '@quillmark/wasm';
|
|
5
5
|
import type { EditorErrorHandler } from '../errors.js';
|
|
6
6
|
import { type TableChromeStrings } from './table-view.js';
|
|
7
|
-
import { type
|
|
7
|
+
import { type SlashState } from './slash.js';
|
|
8
8
|
/** Options for {@link createField}. */
|
|
9
9
|
export interface CreateFieldOpts {
|
|
10
10
|
doc: Document;
|
|
@@ -20,24 +20,22 @@ export interface CreateFieldOpts {
|
|
|
20
20
|
plaintext?: boolean;
|
|
21
21
|
/** Suppress the markdown-shorthand input rules. */
|
|
22
22
|
noInputRules?: boolean;
|
|
23
|
-
/** The island chrome's wording, read
|
|
23
|
+
/** The island chrome's wording, read live (per render) so a consumer swapping
|
|
24
24
|
* locale mid-session re-renders rather than freezing it at mount. Absent leaves
|
|
25
25
|
* the package's English; an inline leaf never asks (it holds no island). */
|
|
26
26
|
tableStrings?: () => TableChromeStrings;
|
|
27
|
-
/** The slash menu's wording, read live for the same reason. */
|
|
28
|
-
slashStrings?: () => SlashStrings;
|
|
29
27
|
/**
|
|
30
28
|
* The slash menu's live state, or `undefined` when it is closed: the channel the
|
|
31
29
|
* chrome draws from (`visual/SlashMenu.svelte`).
|
|
32
30
|
*
|
|
33
|
-
* Its
|
|
31
|
+
* Its presence is what mounts the menu at all. The trigger and its keys are the
|
|
34
32
|
* leaf's, but a surface only a keyboard can reach (claiming Enter, Escape and the
|
|
35
33
|
* arrows with nothing on screen) is worse than no surface, so the door exists
|
|
36
34
|
* exactly where something can draw it. A constrained inline leaf never has one:
|
|
37
35
|
* it holds no island and no block to convert.
|
|
38
36
|
*/
|
|
39
37
|
onSlash?(state: SlashState | undefined): void;
|
|
40
|
-
/** Accessible name → `aria-label` on the `contenteditable`. For a leaf
|
|
38
|
+
/** Accessible name → `aria-label` on the `contenteditable`. For a leaf nothing
|
|
41
39
|
* else names: an array element (the field label plus its 1-based index), the
|
|
42
40
|
* card body (no visible label at all). A leaf with a field label takes
|
|
43
41
|
* `labelledBy` instead: `for` cannot reach a `contenteditable`, which is not a
|
|
@@ -48,7 +46,7 @@ export interface CreateFieldOpts {
|
|
|
48
46
|
/** The parked `description` → `aria-describedby`; announced after the name. */
|
|
49
47
|
describedBy?: string;
|
|
50
48
|
/** Ghost text shown on the empty leaf: the resolved `default:` a field ghosts,
|
|
51
|
-
* or a body's invitation. The
|
|
49
|
+
* or a body's invitation. The initial value; {@link
|
|
52
50
|
* FieldController.setPlaceholder} moves it after mount. Empty/absent
|
|
53
51
|
* shows no ghost. */
|
|
54
52
|
placeholder?: string;
|
|
@@ -56,7 +54,7 @@ export interface CreateFieldOpts {
|
|
|
56
54
|
/** Fired with the new USV caret after an edit or a selection move. */
|
|
57
55
|
onCaretMove?(addr: Addr, pos: number): void;
|
|
58
56
|
/**
|
|
59
|
-
* Fired after an edit
|
|
57
|
+
* Fired after an edit committed to the document: the leaf's change signal, and
|
|
60
58
|
* the reason it is not `onCaretMove`. That one also fires on a bare selection
|
|
61
59
|
* move, so a host driving a recompile off it recompiles on every arrow key.
|
|
62
60
|
*
|
|
@@ -74,7 +72,7 @@ export interface FieldController {
|
|
|
74
72
|
/** The mounted `container`: the leaf's element, for a caller that has to reach the
|
|
75
73
|
* DOM the view sits in (the arrival wash, `core/bloom.ts`) rather than re-find it. */
|
|
76
74
|
readonly el: HTMLElement;
|
|
77
|
-
/** Place the caret at USV `pos` (preview
|
|
75
|
+
/** Place the caret at USV `pos` (preview onPick → usvToPM → here). */
|
|
78
76
|
setCaret(pos: number): void;
|
|
79
77
|
/** External content change → re-hydrate this leaf (gated by reconcile). */
|
|
80
78
|
applyExternal(): void;
|
|
@@ -83,7 +81,7 @@ export interface FieldController {
|
|
|
83
81
|
* its new kind's wording without remounting, which it must not do (the leaf key
|
|
84
82
|
* is the card's session id, so a remount would cost the caret).
|
|
85
83
|
*
|
|
86
|
-
* Refreshes the decoration
|
|
84
|
+
* Refreshes the decoration without a transaction: a placeholder is chrome, and
|
|
87
85
|
* a transaction would fire `onCaretMove` at a moment the caret did not move.
|
|
88
86
|
*/
|
|
89
87
|
setPlaceholder(text: string | undefined): void;
|
|
@@ -103,14 +101,14 @@ export interface FieldController {
|
|
|
103
101
|
/** Ids of the anchors within USV range `[from, to]`: a selection's anchor state. */
|
|
104
102
|
anchorsInRange(from: number, to: number): string[];
|
|
105
103
|
/**
|
|
106
|
-
* Move the slash menu's cursor onto
|
|
107
|
-
* calls. The keyboard cursor and the pointer's highlight are one state, so the
|
|
104
|
+
* Move the slash menu's cursor onto the command `name`: what a pointer entering an
|
|
105
|
+
* item calls. The keyboard cursor and the pointer's highlight are one state, so the
|
|
108
106
|
* chrome moves the one the keys already drive rather than painting a second.
|
|
109
107
|
*/
|
|
110
|
-
slashFocus(
|
|
111
|
-
/** Run
|
|
112
|
-
* no menu open, which is the state a stale click lands in. */
|
|
113
|
-
slashPick(
|
|
108
|
+
slashFocus(name: string): void;
|
|
109
|
+
/** Run the command `name` (a click on its item): the same path Enter takes. A no-op
|
|
110
|
+
* with no menu open, which is the state a stale click lands in. */
|
|
111
|
+
slashPick(name: string): void;
|
|
114
112
|
/** The current PM selection as a USV `{ from, to }` range (the boundary currency). */
|
|
115
113
|
selectionRange(): {
|
|
116
114
|
from: number;
|
|
@@ -123,9 +121,9 @@ export interface FieldController {
|
|
|
123
121
|
* of {@link FieldController}: they are PM internals, and a consumer's contract is the
|
|
124
122
|
* controller's verbs. The format popover is the one caller and it needs both:
|
|
125
123
|
* `view` to name the leaf's own document, `focusedView` to act on wherever the caret
|
|
126
|
-
* actually is, which inside a table island is a
|
|
124
|
+
* actually is, which inside a table island is a nested cell view (`table-view.ts`).
|
|
127
125
|
* The two being different is exactly what tells the popover to withhold its
|
|
128
|
-
* `anchor` button: an anchor is the
|
|
126
|
+
* `anchor` button: an anchor is the field's coordinate space, and a cell is not in it.
|
|
129
127
|
*/
|
|
130
128
|
export interface LeafViews {
|
|
131
129
|
view: EditorView;
|
|
@@ -144,24 +142,21 @@ export declare function createField(opts: CreateFieldOpts): FieldController;
|
|
|
144
142
|
* leaf-specific plugins (`afterHistory`: the addressed leaf passes its
|
|
145
143
|
* anchor-position plugin; a by-value array element passes none), the
|
|
146
144
|
* markdown-shorthand input rules, then the field keymap over the base keymap, and
|
|
147
|
-
* last the two that answer to a caret or a selection
|
|
145
|
+
* last the two that answer to a caret or a selection beside a block: the gap cursor
|
|
148
146
|
* and {@link pastAtomPlugin}.
|
|
149
147
|
*
|
|
150
148
|
* A `plaintext` field carries no marks (decode strips them, the keymap suppresses
|
|
151
149
|
* Mod-b/i/u), so it also skips the input rules: `inlineSchema` still declares the
|
|
152
|
-
* mark types, so a `**bold**` rule would apply a strong mark
|
|
153
|
-
* delimiters. The inline schema has no block nodes, so those rules are the
|
|
150
|
+
* mark types, so a `**bold**` rule would apply a strong mark and eat the literal
|
|
151
|
+
* delimiters. The inline schema has no block nodes, so those rules are the only
|
|
154
152
|
* ones it would add: skip all.
|
|
155
153
|
*/
|
|
156
154
|
export declare function proseLeafPlugins(schema: Schema, opts: {
|
|
157
155
|
inline: boolean;
|
|
158
156
|
plaintext: boolean;
|
|
159
|
-
/** The slash menu's
|
|
160
|
-
*
|
|
161
|
-
slash?:
|
|
162
|
-
items: () => SlashItem[];
|
|
163
|
-
onState: (state: SlashState | undefined) => void;
|
|
164
|
-
};
|
|
157
|
+
/** The slash menu's report channel; absent mounts no menu and leaves
|
|
158
|
+
* Enter/Escape/the arrows to the links below (`keymap.ts`). */
|
|
159
|
+
slash?: (state: SlashState | undefined) => void;
|
|
165
160
|
noInputRules?: boolean;
|
|
166
161
|
/** Read live, not captured: the ghost can move after mount
|
|
167
162
|
* ({@link FieldController.setPlaceholder}), and a re-hydration rebuilds this
|