@quillmark/svelte 0.5.1 → 0.7.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 +6 -0
- package/THEMING.md +13 -17
- package/dist/core/address.d.ts +17 -3
- package/dist/core/address.js +20 -3
- package/dist/core/codec/blocks.d.ts +37 -0
- package/dist/core/codec/blocks.js +163 -0
- package/dist/core/codec/field.js +5 -6
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +3 -2
- package/dist/core/codec/inputrules.js +24 -144
- package/dist/core/codec/prose.css +14 -25
- package/dist/core/codec/schema.d.ts +8 -0
- package/dist/core/codec/schema.js +33 -1
- package/dist/core/codec/slash.d.ts +18 -16
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +4 -0
- package/dist/core/codec/table-view.js +137 -158
- package/dist/core/codec/table.d.ts +0 -10
- package/dist/core/codec/table.js +0 -15
- package/dist/core/theme.css +357 -346
- package/dist/preset/recipes.css +25 -21
- package/dist/preview/Preview.svelte +4 -2
- package/dist/preview/bridge.d.ts +23 -0
- package/dist/preview/bridge.js +92 -35
- package/dist/preview/controller.d.ts +6 -5
- package/dist/preview/controller.js +13 -7
- package/dist/preview/geometry.d.ts +10 -9
- package/dist/preview/geometry.js +11 -13
- package/dist/preview/index.js +3 -2
- package/dist/preview/paint.js +9 -1
- package/dist/visual/ArrayField.svelte +246 -63
- package/dist/visual/ArrayField.svelte.d.ts +5 -1
- package/dist/visual/Card.svelte +84 -50
- package/dist/visual/EnumField.svelte +37 -3
- package/dist/visual/Field.svelte +28 -4
- package/dist/visual/FieldLabel.svelte +2 -2
- package/dist/visual/FormatPopover.svelte +13 -1
- package/dist/visual/ObjectField.svelte +128 -35
- package/dist/visual/ObjectField.svelte.d.ts +11 -4
- package/dist/visual/ProseArrayElement.svelte +22 -2
- package/dist/visual/ProseArrayElement.svelte.d.ts +1 -0
- package/dist/visual/SlashMenu.svelte +7 -0
- package/dist/visual/TipsCard.svelte +33 -13
- package/dist/visual/VariantField.svelte +95 -0
- package/dist/visual/VariantField.svelte.d.ts +21 -0
- package/dist/visual/VisualEditorInner.svelte +44 -21
- package/dist/visual/controls.css +282 -283
- package/dist/visual/diagnostics.d.ts +13 -11
- package/dist/visual/diagnostics.js +11 -9
- package/dist/visual/domid.d.ts +13 -0
- package/dist/visual/domid.js +16 -0
- package/dist/visual/hold.d.ts +17 -0
- package/dist/visual/hold.js +39 -0
- package/dist/visual/index.js +1 -1
- package/dist/visual/leaves.d.ts +9 -7
- package/dist/visual/links.d.ts +5 -2
- package/dist/visual/links.js +9 -3
- package/dist/visual/motion.d.ts +9 -0
- package/dist/visual/motion.js +10 -2
- package/dist/visual/strings.d.ts +15 -0
- package/dist/visual/strings.js +3 -0
- package/dist/visual/structure.d.ts +32 -6
- package/dist/visual/structure.js +49 -3
- package/package.json +2 -2
- package/dist/core/codec/clipboard.d.ts +0 -59
- package/dist/core/codec/clipboard.js +0 -202
|
@@ -1,202 +0,0 @@
|
|
|
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
|
-
}
|