@quillmark/svelte 0.23.0 → 0.25.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 +7 -8
- package/THEMING.md +1 -1
- package/dist/core/codec/blocks.d.ts +23 -6
- package/dist/core/codec/blocks.js +163 -38
- package/dist/core/codec/code.js +3 -8
- package/dist/core/codec/decode.d.ts +2 -1
- package/dist/core/codec/decode.js +19 -11
- package/dist/core/codec/field.d.ts +6 -3
- package/dist/core/codec/field.js +12 -4
- package/dist/core/codec/index.d.ts +2 -0
- package/dist/core/codec/index.js +4 -0
- package/dist/core/codec/inputrules.js +40 -10
- package/dist/core/codec/islands.d.ts +4 -1
- package/dist/core/codec/islands.js +43 -16
- package/dist/core/codec/keymap.js +19 -1
- package/dist/core/codec/line-paste.d.ts +7 -0
- package/dist/core/codec/line-paste.js +36 -0
- package/dist/core/codec/list-move.d.ts +6 -0
- package/dist/core/codec/list-move.js +30 -0
- package/dist/core/codec/list-paste.d.ts +2 -0
- package/dist/core/codec/list-paste.js +142 -0
- package/dist/core/codec/list-toggle.d.ts +4 -0
- package/dist/core/codec/list-toggle.js +170 -0
- package/dist/core/codec/lists.d.ts +22 -15
- package/dist/core/codec/lists.js +404 -97
- package/dist/core/codec/markdown-paste.d.ts +13 -3
- package/dist/core/codec/markdown-paste.js +70 -6
- package/dist/core/codec/nesting.d.ts +5 -1
- package/dist/core/codec/nesting.js +1 -1
- package/dist/core/codec/prose.css +36 -20
- package/dist/core/codec/slash.d.ts +2 -1
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/touching.d.ts +12 -0
- package/dist/core/codec/touching.js +33 -0
- package/dist/core/codec/undo.d.ts +5 -0
- package/dist/core/codec/undo.js +21 -0
- package/dist/index.js +1 -1
- package/dist/preview/Preview.svelte +88 -13
- package/dist/preview/Preview.svelte.d.ts +2 -2
- package/dist/preview/bridge.js +8 -2
- package/dist/preview/controller.d.ts +8 -4
- package/dist/preview/controller.js +10 -2
- package/dist/preview/index.js +1 -1
- package/dist/preview/paint.d.ts +6 -2
- package/dist/preview/paint.js +67 -56
- package/dist/preview/strings.d.ts +2 -0
- package/dist/preview/strings.js +4 -2
- package/dist/preview/viewport.d.ts +10 -0
- package/dist/preview/viewport.js +271 -0
- package/dist/preview/zoom.d.ts +41 -0
- package/dist/preview/zoom.js +52 -0
- package/dist/visual/ArrayField.svelte +24 -4
- package/dist/visual/Card.svelte +5 -4
- package/dist/visual/DateField.svelte +26 -13
- package/dist/visual/Field.svelte +2 -2
- package/dist/visual/FormatPopover.svelte +41 -2
- package/dist/visual/ObjectField.svelte +4 -7
- package/dist/visual/ObjectField.svelte.d.ts +2 -2
- package/dist/visual/ProseValue.svelte +35 -3
- package/dist/visual/ProseValue.svelte.d.ts +7 -0
- package/dist/visual/VisualEditorInner.svelte +5 -11
- package/dist/visual/icons/nodes.d.ts +26 -0
- package/dist/visual/icons/nodes.js +16 -0
- package/dist/visual/strings.d.ts +6 -4
- package/dist/visual/strings.js +9 -3
- package/dist/visual/structure.d.ts +22 -10
- package/dist/visual/structure.js +35 -11
- package/package.json +2 -2
package/dist/visual/Card.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
One card block, its
|
|
2
|
+
One card block, its top-level objects (or legacy `ui.group`s) drawn as panels and its
|
|
3
|
+
fields packed by `ui.compact`. `main` is headerless
|
|
3
4
|
and takes no controls. Every prose leaf takes a parent-built live address, so a card
|
|
4
5
|
reorder re-targets its commits without a remount.
|
|
5
6
|
-->
|
|
@@ -273,9 +274,9 @@
|
|
|
273
274
|
{@const headIds = section.head ? ops.domIds(section.head.name) : undefined}
|
|
274
275
|
{@const headHint = section.head?.description}
|
|
275
276
|
<div class={['qm-group', { 'qm-open': isOpen }]}>
|
|
276
|
-
<!-- A
|
|
277
|
-
takes its label id and parks its description: the field
|
|
278
|
-
label of its own to say the section's name twice. -->
|
|
277
|
+
<!-- A panel's object, or an object alone in its section, is named by the
|
|
278
|
+
header, which takes its label id and parks its description: the field
|
|
279
|
+
draws no label of its own to say the section's name twice. -->
|
|
279
280
|
<div class="qm-group-head">
|
|
280
281
|
<button
|
|
281
282
|
type="button"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
A `date` (or `datetime`) field → a styled segmented date field on bits-ui. The
|
|
3
|
-
stored value is a string, `YYYY-MM-DD` or `today`; a cleared control commits as
|
|
4
|
-
|
|
3
|
+
stored value is a string, `YYYY-MM-DD` or `today`; a cleared control commits as its
|
|
4
|
+
last segment empties, the blank `""` over a `default:`, which stays blank, and `undefined`
|
|
5
5
|
(the unset rung) where nothing is declared, the parent removing the field. The value-object a date field lowers to is a render-time
|
|
6
6
|
concern: the editor only sees the stored string.
|
|
7
7
|
|
|
@@ -48,12 +48,14 @@
|
|
|
48
48
|
A segment edit that leaves a segment empty empties the primitive's value, which is
|
|
49
49
|
held rather than committed while the focus stays inside: the first Backspace through
|
|
50
50
|
a date writes nothing unset, and the empty segment shows its hint, not the default's
|
|
51
|
-
digits beside the ones still standing.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
digits beside the ones still standing. The keystroke that empties the last segment
|
|
52
|
+
writes the blank at once, so the render follows the clear without waiting on the
|
|
53
|
+
focus. Leaving with a segment filled puts the stored date back; a field storing no
|
|
54
|
+
date puts its segments back by remounting the primitive, which re-reads an unchanged
|
|
55
|
+
empty value as nothing to sync.
|
|
55
56
|
-->
|
|
56
57
|
<script lang="ts">
|
|
58
|
+
import { flushSync } from 'svelte';
|
|
57
59
|
import { DateField as BitsDateField } from 'bits-ui';
|
|
58
60
|
import { getLocalTimeZone, parseDate, today, type DateValue } from '@internationalized/date';
|
|
59
61
|
import Icon from './icons/Icon.svelte';
|
|
@@ -164,18 +166,28 @@
|
|
|
164
166
|
function enter(e: FocusEvent): void {
|
|
165
167
|
if (!within(e) && value == null && fallbackDate) seat = true;
|
|
166
168
|
}
|
|
169
|
+
const filled = (): boolean =>
|
|
170
|
+
[...(wrapEl?.querySelectorAll('[data-segment]') ?? [])].some(
|
|
171
|
+
(s) => s.getAttribute('data-segment') !== 'literal' && !s.hasAttribute('data-ghosted')
|
|
172
|
+
);
|
|
173
|
+
/** Every segment empty: the blank, written at once. */
|
|
174
|
+
function clear(): void {
|
|
175
|
+
const blank = fallbackDate ? '' : undefined;
|
|
176
|
+
if (value !== blank) onCommit(blank);
|
|
177
|
+
}
|
|
167
178
|
function leave(e: FocusEvent): void {
|
|
168
179
|
if (within(e)) return;
|
|
169
180
|
seat = false;
|
|
170
181
|
if (!held) return;
|
|
171
182
|
held = false;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
if (!filled()) clear();
|
|
184
|
+
else if (!value) generation++;
|
|
185
|
+
}
|
|
186
|
+
/** The keystroke that empties the last segment, after the primitive has drawn it. */
|
|
187
|
+
function settle(): void {
|
|
188
|
+
if (!held) return;
|
|
189
|
+
flushSync();
|
|
190
|
+
if (!filled()) clear();
|
|
179
191
|
}
|
|
180
192
|
|
|
181
193
|
// What one segment prints, and which rung it takes.
|
|
@@ -249,6 +261,7 @@
|
|
|
249
261
|
aria-describedby={describedBy}
|
|
250
262
|
data-ghosted={ghost ? '' : undefined}
|
|
251
263
|
data-default={seated || follows ? '' : undefined}
|
|
264
|
+
onkeydown={settle}
|
|
252
265
|
>
|
|
253
266
|
{#snippet children({ segments })}
|
|
254
267
|
<!-- Keyed by index: `part` repeats (the `literal` separators between
|
package/dist/visual/Field.svelte
CHANGED
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
VARIANT_DISCRIMINANT,
|
|
34
34
|
arrayLayout,
|
|
35
35
|
baseType,
|
|
36
|
-
flatObject,
|
|
37
36
|
ghostDefault,
|
|
38
37
|
isContainer,
|
|
39
38
|
optionalCell,
|
|
@@ -174,7 +173,8 @@
|
|
|
174
173
|
);
|
|
175
174
|
// Two controls own their label row, the add chip and the held count sharing it.
|
|
176
175
|
const ownsLabel = $derived(field.control === 'array' || field.control === 'matrix');
|
|
177
|
-
|
|
176
|
+
// A headed object is a panel's, and the panel is its frame.
|
|
177
|
+
const flat = $derived(headed);
|
|
178
178
|
|
|
179
179
|
// A container walks its diagnostics down to the cell each names and draws the rest
|
|
180
180
|
// at its own foot (`diagnostics.ts`); every other control draws them here, under
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
(`ProseValue`). This component never touches the content. The keymap mirror
|
|
13
13
|
(Mod-b/i/u) lives in the codec's keymap (field.ts); this is the pointer
|
|
14
14
|
affordance for all six marks. Mod-K is this surface's own key, the prompt it raises
|
|
15
|
-
being this surface's: a document `keydown` the leaf's keymap left unclaimed.
|
|
15
|
+
being this surface's: a document `keydown` the leaf's keymap left unclaimed. Over a
|
|
16
|
+
leaf that holds lists, two more buttons number or bullet every selected line, or take
|
|
17
|
+
the marker off where every one carries it: the codec's `toggleList`, whose keys
|
|
18
|
+
(Mod-Shift-7/8) the leaf's keymap binds.
|
|
16
19
|
|
|
17
20
|
Selection observation. The view's `dispatchTransaction` is the leaf's
|
|
18
21
|
(read-only), so there is no hook to observe transactions from outside.
|
|
@@ -72,7 +75,7 @@
|
|
|
72
75
|
import { Popover } from 'bits-ui';
|
|
73
76
|
import Icon from './icons/Icon.svelte';
|
|
74
77
|
import type { IconName } from './icons/nodes.js';
|
|
75
|
-
import { hasMarks, rangeAnchor, type RangeAnchor } from '../core/codec/index.js';
|
|
78
|
+
import { hasMarks, rangeAnchor, toggleList, type RangeAnchor } from '../core/codec/index.js';
|
|
76
79
|
import type { ActiveProse } from './leaves.js';
|
|
77
80
|
import { clearLink, hrefInSelection, normalizeHref, setLink } from './links.js';
|
|
78
81
|
import { spacePx } from '../core/rungs.js';
|
|
@@ -96,7 +99,15 @@
|
|
|
96
99
|
{ name: 'link', icon: 'link', title: t.strings.formatLink }
|
|
97
100
|
]);
|
|
98
101
|
|
|
102
|
+
const LISTS: { ordered: boolean; icon: IconName; title: string }[] = $derived([
|
|
103
|
+
{ ordered: true, icon: 'list-ordered', title: t.strings.formatNumberedList },
|
|
104
|
+
{ ordered: false, icon: 'list', title: t.strings.formatBulletedList }
|
|
105
|
+
]);
|
|
106
|
+
|
|
99
107
|
let open = $state(false);
|
|
108
|
+
/** Whether the leaf under the selection holds lists: a body does, a one-line field
|
|
109
|
+
* and a table cell do not. */
|
|
110
|
+
let listable = $state(false);
|
|
100
111
|
/** A pointer is down outside this surface: the selection is still being made. */
|
|
101
112
|
let pressed = false;
|
|
102
113
|
let linkPromptOpen = $state(false);
|
|
@@ -161,6 +172,7 @@
|
|
|
161
172
|
return;
|
|
162
173
|
}
|
|
163
174
|
portalTarget = view.dom.closest<HTMLElement>('[data-qm-root]') ?? undefined;
|
|
175
|
+
listable = !!view.state.schema.nodes.list_item;
|
|
164
176
|
const { from, to } = view.state.selection;
|
|
165
177
|
anchor = rangeAnchor(view, from, to);
|
|
166
178
|
open = true;
|
|
@@ -268,6 +280,14 @@
|
|
|
268
280
|
sync();
|
|
269
281
|
}
|
|
270
282
|
|
|
283
|
+
function toggleLines(ordered: boolean): void {
|
|
284
|
+
const view = activeLeafView();
|
|
285
|
+
if (!view) return;
|
|
286
|
+
toggleList(ordered)(view.state, view.dispatch);
|
|
287
|
+
view.focus();
|
|
288
|
+
sync();
|
|
289
|
+
}
|
|
290
|
+
|
|
271
291
|
/** Every exit from the prompt: lower it, run `cmd` if there is one, and hand the
|
|
272
292
|
* leaf back its focus and the surface its buttons. */
|
|
273
293
|
function closePrompt(cmd?: Command): void {
|
|
@@ -421,6 +441,19 @@
|
|
|
421
441
|
onclick={() => toggle(m.name)}><Icon name={m.icon} /></button
|
|
422
442
|
>
|
|
423
443
|
{/each}
|
|
444
|
+
{#if listable}
|
|
445
|
+
<span class="qm-format-divider" aria-hidden="true"></span>
|
|
446
|
+
{#each LISTS as l (l.icon)}
|
|
447
|
+
<button
|
|
448
|
+
type="button"
|
|
449
|
+
class="qm-icon-btn qm-mark-btn qm-focus-ring"
|
|
450
|
+
title={l.title}
|
|
451
|
+
aria-label={l.title}
|
|
452
|
+
onmousedown={keepFocus}
|
|
453
|
+
onclick={() => toggleLines(l.ordered)}><Icon name={l.icon} /></button
|
|
454
|
+
>
|
|
455
|
+
{/each}
|
|
456
|
+
{/if}
|
|
424
457
|
</div>
|
|
425
458
|
{/if}
|
|
426
459
|
</div>
|
|
@@ -452,6 +485,12 @@
|
|
|
452
485
|
|
|
453
486
|
No state for a mark the selection already carries: a run may carry one over part of
|
|
454
487
|
itself, which a two-state button has no third face for. */
|
|
488
|
+
.qm-format-divider {
|
|
489
|
+
align-self: stretch;
|
|
490
|
+
width: 1px;
|
|
491
|
+
margin: var(--_qm-space-half) 0;
|
|
492
|
+
background: var(--_qm-rule);
|
|
493
|
+
}
|
|
455
494
|
.qm-mark-btn {
|
|
456
495
|
padding: var(--_qm-space) var(--_qm-space-2);
|
|
457
496
|
color: var(--_qm-ink);
|
|
@@ -30,9 +30,8 @@
|
|
|
30
30
|
member's id. So a cell reads at whatever depth it sits, which nothing here knows.
|
|
31
31
|
|
|
32
32
|
`bare` is the table's row: the same cells with no labels and no figure of their own,
|
|
33
|
-
laid onto the tracks the table's header names ({@link ArrayField}). `flat` is
|
|
34
|
-
|
|
35
|
-
depth of whatever holds the subform.
|
|
33
|
+
laid onto the tracks the table's header names ({@link ArrayField}). `flat` is a
|
|
34
|
+
panel's object: the cells keep their labels and lose the figure, the panel being it.
|
|
36
35
|
-->
|
|
37
36
|
<script lang="ts">
|
|
38
37
|
import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
@@ -42,7 +41,6 @@
|
|
|
42
41
|
controlKind,
|
|
43
42
|
declaredContent,
|
|
44
43
|
fieldModels,
|
|
45
|
-
flatObject,
|
|
46
44
|
humanize,
|
|
47
45
|
isContainer,
|
|
48
46
|
literalText,
|
|
@@ -103,8 +101,8 @@
|
|
|
103
101
|
/** A table's row: the cells alone, on the tracks the table's header names, with
|
|
104
102
|
* no labels and no vertical of their own. */
|
|
105
103
|
bare?: boolean;
|
|
106
|
-
/**
|
|
107
|
-
*
|
|
104
|
+
/** A panel's object: no vertical and no rung, the properties at the depth of the
|
|
105
|
+
* panel's field list. */
|
|
108
106
|
flat?: boolean;
|
|
109
107
|
/** A card-level object, or one nested in it: its properties are placed as a
|
|
110
108
|
* card's fields are (`placeFields`), so moving a field into a group leaves its row
|
|
@@ -426,7 +424,6 @@
|
|
|
426
424
|
bind:this={nestedEls[key]}
|
|
427
425
|
value={obj[key] as Record<string, unknown> | undefined}
|
|
428
426
|
properties={sub.properties}
|
|
429
|
-
flat={flatObject(sub)}
|
|
430
427
|
{placed}
|
|
431
428
|
label={ids ? undefined : fallbackName(key, sub)}
|
|
432
429
|
idBase={ids?.control}
|
|
@@ -32,8 +32,8 @@ interface Props {
|
|
|
32
32
|
/** A table's row: the cells alone, on the tracks the table's header names, with
|
|
33
33
|
* no labels and no vertical of their own. */
|
|
34
34
|
bare?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
35
|
+
/** A panel's object: no vertical and no rung, the properties at the depth of the
|
|
36
|
+
* panel's field list. */
|
|
37
37
|
flat?: boolean;
|
|
38
38
|
/** A card-level object, or one nested in it: its properties are placed as a
|
|
39
39
|
* card's fields are (`placeFields`), so moving a field into a group leaves its row
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
declaring `multiline: true` takes the full row ({@link ObjectField}), a `richtext` one
|
|
32
32
|
on the block schema and a `text` one on the plain schema, paragraphs and hard breaks.
|
|
33
33
|
An array element is one textblock whatever it declares, since Enter there is the
|
|
34
|
-
repeater's
|
|
34
|
+
repeater's, and a paste of several lines into one is a row per line, as Enter would
|
|
35
|
+
open them, up to the array's `max:`, the last row taking what is left joined
|
|
36
|
+
(`lines`). There is no slash menu at either width, and no island view.
|
|
35
37
|
|
|
36
38
|
A leaf over content it cannot edit is held (`fitsLeaf`): a narrowed one over anything
|
|
37
39
|
but one plain paragraph, a plain one over anything but plain lines, where the decode
|
|
@@ -43,6 +45,7 @@
|
|
|
43
45
|
-->
|
|
44
46
|
<script lang="ts">
|
|
45
47
|
import { untrack } from 'svelte';
|
|
48
|
+
import { Slice, type Node as PMNode } from 'prosemirror-model';
|
|
46
49
|
import { EditorState, Selection } from 'prosemirror-state';
|
|
47
50
|
import { EditorView } from 'prosemirror-view';
|
|
48
51
|
import {
|
|
@@ -52,7 +55,9 @@
|
|
|
52
55
|
emptyContent,
|
|
53
56
|
fitsLeaf,
|
|
54
57
|
heldAttributes,
|
|
58
|
+
joinLines,
|
|
55
59
|
leafSchema,
|
|
60
|
+
pastedLines,
|
|
56
61
|
pmToContent,
|
|
57
62
|
proseAttributes,
|
|
58
63
|
proseLeafPlugins,
|
|
@@ -103,6 +108,10 @@
|
|
|
103
108
|
/** The declared `max:`, a character count no edit takes the leaf past. */
|
|
104
109
|
cap?: TextCap;
|
|
105
110
|
onChange: (rt: Content) => void;
|
|
111
|
+
/** Where a paste's lines past the first go: rows of their own below this one, at
|
|
112
|
+
* most `room()` of them, the last taking the rest joined. The row's own edit comes
|
|
113
|
+
* up beside them, the two one commit. Absent, the leaf joins them into its line. */
|
|
114
|
+
lines?: { room: () => number; onLines: (rt: Content, below: Content[]) => void };
|
|
106
115
|
/** Raw keydown, for a container whose own keys run through this leaf: the
|
|
107
116
|
* array repeater's Enter/Backspace (`ArrayField`). Fires before the view's own
|
|
108
117
|
* keymap, so the state it reads is the one this keystroke has yet to change. */
|
|
@@ -120,6 +129,7 @@
|
|
|
120
129
|
placeholder,
|
|
121
130
|
cap,
|
|
122
131
|
onChange,
|
|
132
|
+
lines,
|
|
123
133
|
onKey
|
|
124
134
|
}: Props = $props();
|
|
125
135
|
|
|
@@ -177,6 +187,14 @@
|
|
|
177
187
|
let seen = value;
|
|
178
188
|
let defaulted = stored === undefined && !!fallback;
|
|
179
189
|
const reconciler = createReconciler(shown(stored));
|
|
190
|
+
// The rows a paste's later lines become, held from the parse to the edit that
|
|
191
|
+
// lands the first line.
|
|
192
|
+
let spill: Content[] | undefined;
|
|
193
|
+
const row = (group: PMNode[]): Content => {
|
|
194
|
+
const { schema } = mounted.state;
|
|
195
|
+
const line = schema.nodes.paragraph.create(null, joinLines(group, schema));
|
|
196
|
+
return pmToContent(schema.node('doc', null, [line]));
|
|
197
|
+
};
|
|
180
198
|
held = !fitsLeaf(reconciler.last, leaf);
|
|
181
199
|
// Which of `aria-label` / `aria-labelledby` wins is the codec's one answer
|
|
182
200
|
// (`proseAttributes`), so a cell carrying a label element and a row carrying
|
|
@@ -208,14 +226,28 @@
|
|
|
208
226
|
const named = held ? heldNamed() : plainNamed();
|
|
209
227
|
return defaulted ? { ...named, 'data-default': '' } : named;
|
|
210
228
|
},
|
|
229
|
+
transformPasted: (slice) => {
|
|
230
|
+
spill = undefined;
|
|
231
|
+
if (!lines || held) return slice;
|
|
232
|
+
const pasted = pastedLines(slice);
|
|
233
|
+
const room = Math.min(pasted.length - 1, lines.room());
|
|
234
|
+
if (room < 1) return slice;
|
|
235
|
+
const below = pasted.slice(1);
|
|
236
|
+
spill = [...below.slice(0, room - 1).map((l) => row([l])), row(below.slice(room - 1))];
|
|
237
|
+
return new Slice(pasted[0]!.content, 0, 0);
|
|
238
|
+
},
|
|
211
239
|
dispatchTransaction(tr) {
|
|
240
|
+
const event = tr.getMeta('uiEvent');
|
|
241
|
+
const below = event === 'paste' || event === 'drop' ? spill : undefined;
|
|
242
|
+
spill = undefined;
|
|
212
243
|
const next = mounted.state.apply(tr);
|
|
213
244
|
if (next === mounted.state) return;
|
|
214
245
|
const edit = tr.docChanged && !held;
|
|
215
246
|
if (edit) defaulted = false;
|
|
216
247
|
mounted.updateState(next);
|
|
217
|
-
if (!edit) return;
|
|
218
|
-
|
|
248
|
+
if (!edit && !below) return;
|
|
249
|
+
if (below && lines) lines.onLines(pmToContent(next.doc), below);
|
|
250
|
+
else onChange(pmToContent(next.doc));
|
|
219
251
|
// The commit as the store took it, so its echo through the re-derive is no move.
|
|
220
252
|
seen = value;
|
|
221
253
|
reconciler.commit(shown(content()));
|
|
@@ -35,6 +35,13 @@ interface Props {
|
|
|
35
35
|
/** The declared `max:`, a character count no edit takes the leaf past. */
|
|
36
36
|
cap?: TextCap;
|
|
37
37
|
onChange: (rt: Content) => void;
|
|
38
|
+
/** Where a paste's lines past the first go: rows of their own below this one, at
|
|
39
|
+
* most `room()` of them, the last taking the rest joined. The row's own edit comes
|
|
40
|
+
* up beside them, the two one commit. Absent, the leaf joins them into its line. */
|
|
41
|
+
lines?: {
|
|
42
|
+
room: () => number;
|
|
43
|
+
onLines: (rt: Content, below: Content[]) => void;
|
|
44
|
+
};
|
|
38
45
|
/** Raw keydown, for a container whose own keys run through this leaf: the
|
|
39
46
|
* array repeater's Enter/Backspace (`ArrayField`). Fires before the view's own
|
|
40
47
|
* keymap, so the state it reads is the one this keystroke has yet to change. */
|
|
@@ -54,10 +54,7 @@
|
|
|
54
54
|
isContainer,
|
|
55
55
|
shortCell,
|
|
56
56
|
schemaAt,
|
|
57
|
-
|
|
58
|
-
groupOrder,
|
|
59
|
-
groupSections,
|
|
60
|
-
groupLabel,
|
|
57
|
+
cardSections,
|
|
61
58
|
cardTitle,
|
|
62
59
|
kindTitle,
|
|
63
60
|
fieldValues,
|
|
@@ -325,9 +322,9 @@
|
|
|
325
322
|
try {
|
|
326
323
|
if (value === undefined) {
|
|
327
324
|
// The unset rung (VISUAL_EDITOR §"The commitment ladder"): removing the field
|
|
328
|
-
// leaves
|
|
325
|
+
// leaves Quillmark's authored › `default:` › blank-fill resolve to render the
|
|
329
326
|
// default, where writing one bakes a snapshot the schema cannot track (canon
|
|
330
|
-
// SCHEMAS.md:
|
|
327
|
+
// SCHEMAS.md: Quillmark never writes a default; the editor writes one only
|
|
331
328
|
// as the edit that takes it). Removal writes no value, so there is
|
|
332
329
|
// nothing for a schema to conform and the lane is the quill-free one.
|
|
333
330
|
if (isMain) {
|
|
@@ -632,14 +629,11 @@
|
|
|
632
629
|
payloadItems: readonly PayloadItem[];
|
|
633
630
|
ext?: Record<string, unknown>;
|
|
634
631
|
},
|
|
635
|
-
cardSchema: Parameters<typeof
|
|
632
|
+
cardSchema: Parameters<typeof cardSections>[0] | undefined,
|
|
636
633
|
rows: ResolvedCardRows
|
|
637
634
|
): CardModel {
|
|
638
635
|
const values = fieldValues(card.payloadItems);
|
|
639
|
-
const
|
|
640
|
-
const sections = cardSchema
|
|
641
|
-
? groupSections(fields, groupOrder(cardSchema), (g) => groupLabel(cardSchema, g))
|
|
642
|
-
: [];
|
|
636
|
+
const sections = cardSchema ? cardSections(cardSchema) : [];
|
|
643
637
|
const extEditor = card.ext?.editor as { title?: string } | undefined;
|
|
644
638
|
const hasBody = bodyEnabled(cardSchema);
|
|
645
639
|
return {
|
|
@@ -82,6 +82,32 @@ export declare const ICONS: {
|
|
|
82
82
|
}], readonly ["path", {
|
|
83
83
|
readonly d: "m8 6-6 6 6 6";
|
|
84
84
|
}]];
|
|
85
|
+
readonly list: readonly [readonly ["path", {
|
|
86
|
+
readonly d: "M3 5h.01";
|
|
87
|
+
}], readonly ["path", {
|
|
88
|
+
readonly d: "M3 12h.01";
|
|
89
|
+
}], readonly ["path", {
|
|
90
|
+
readonly d: "M3 19h.01";
|
|
91
|
+
}], readonly ["path", {
|
|
92
|
+
readonly d: "M8 5h13";
|
|
93
|
+
}], readonly ["path", {
|
|
94
|
+
readonly d: "M8 12h13";
|
|
95
|
+
}], readonly ["path", {
|
|
96
|
+
readonly d: "M8 19h13";
|
|
97
|
+
}]];
|
|
98
|
+
readonly 'list-ordered': readonly [readonly ["path", {
|
|
99
|
+
readonly d: "M11 5h10";
|
|
100
|
+
}], readonly ["path", {
|
|
101
|
+
readonly d: "M11 12h10";
|
|
102
|
+
}], readonly ["path", {
|
|
103
|
+
readonly d: "M11 19h10";
|
|
104
|
+
}], readonly ["path", {
|
|
105
|
+
readonly d: "M4 4h1v5";
|
|
106
|
+
}], readonly ["path", {
|
|
107
|
+
readonly d: "M4 9h2";
|
|
108
|
+
}], readonly ["path", {
|
|
109
|
+
readonly d: "M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02";
|
|
110
|
+
}]];
|
|
85
111
|
readonly link: readonly [readonly ["path", {
|
|
86
112
|
readonly d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71";
|
|
87
113
|
}], readonly ["path", {
|
|
@@ -41,6 +41,22 @@ export const ICONS = {
|
|
|
41
41
|
['path', { d: 'm16 18 6-6-6-6' }],
|
|
42
42
|
['path', { d: 'm8 6-6 6 6 6' }]
|
|
43
43
|
],
|
|
44
|
+
list: [
|
|
45
|
+
['path', { d: 'M3 5h.01' }],
|
|
46
|
+
['path', { d: 'M3 12h.01' }],
|
|
47
|
+
['path', { d: 'M3 19h.01' }],
|
|
48
|
+
['path', { d: 'M8 5h13' }],
|
|
49
|
+
['path', { d: 'M8 12h13' }],
|
|
50
|
+
['path', { d: 'M8 19h13' }]
|
|
51
|
+
],
|
|
52
|
+
'list-ordered': [
|
|
53
|
+
['path', { d: 'M11 5h10' }],
|
|
54
|
+
['path', { d: 'M11 12h10' }],
|
|
55
|
+
['path', { d: 'M11 19h10' }],
|
|
56
|
+
['path', { d: 'M4 4h1v5' }],
|
|
57
|
+
['path', { d: 'M4 9h2' }],
|
|
58
|
+
['path', { d: 'M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02' }]
|
|
59
|
+
],
|
|
44
60
|
link: [
|
|
45
61
|
['path', { d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' }],
|
|
46
62
|
['path', { d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71' }]
|
package/dist/visual/strings.d.ts
CHANGED
|
@@ -79,6 +79,8 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
79
79
|
formatStrikethrough: string;
|
|
80
80
|
formatCode: string;
|
|
81
81
|
formatLink: string;
|
|
82
|
+
formatNumberedList: string;
|
|
83
|
+
formatBulletedList: string;
|
|
82
84
|
/** The anchor button's accessible name. The popover withholds the button, so
|
|
83
85
|
* nothing draws this or the title below. */
|
|
84
86
|
formatAnchor: string;
|
|
@@ -122,7 +124,7 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
122
124
|
*
|
|
123
125
|
* - **`validation::enum_violation`, `type_mismatch`, `format_violation`**: the
|
|
124
126
|
* constraint re-words from the quill's schema at `path`, the offending value from
|
|
125
|
-
* `args` (`value` / `sourceToken` / `actual`,
|
|
127
|
+
* `args` (`value` / `sourceToken` / `actual`, Quillmark testifying to what it
|
|
126
128
|
* saw) and never from the document at `path`: validation runs post-coercion, so
|
|
127
129
|
* the validator read a value the document does not hold and a sentence built from
|
|
128
130
|
* `path` names a spelling the user never typed.
|
|
@@ -130,7 +132,7 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
130
132
|
* refused value being in neither document (unchanged on throw) nor schema.
|
|
131
133
|
* - **`parse::yaml_error_with_location`, `invalid_structure`**: does not re-word. No
|
|
132
134
|
* `path`; `args` names the block (`blockIndex`) and `location` places the failure in
|
|
133
|
-
* the document's own coordinates,
|
|
135
|
+
* the document's own coordinates, Quillmark prose riding under no key: the parser's own
|
|
134
136
|
* text, its column and its caret snippet exist only inside `message`.
|
|
135
137
|
* - **`LiveSession.warnings`**: does not re-word. Backend text, an external feed.
|
|
136
138
|
*
|
|
@@ -145,8 +147,8 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
145
147
|
export type FormatDiagnostic = (d: Diagnostic) => string | undefined;
|
|
146
148
|
/**
|
|
147
149
|
* A `Mod` chord as the writer's platform prints it: `⌘B` on Apple's, `Ctrl+B`
|
|
148
|
-
* elsewhere
|
|
149
|
-
* tooltip names the chord the keymap binds.
|
|
150
|
+
* elsewhere, and `Shift-7` as `⇧⌘7` or `Ctrl+Shift+7`. The platform test is the one
|
|
151
|
+
* `prosemirror-keymap` resolves `Mod` by, so a tooltip names the chord the keymap binds.
|
|
150
152
|
*/
|
|
151
153
|
export declare function chord(key: string, platform?: string): string;
|
|
152
154
|
/** The package's English. Every key, so the merge below is total. */
|
package/dist/visual/strings.js
CHANGED
|
@@ -21,11 +21,15 @@ import { getContext, setContext } from 'svelte';
|
|
|
21
21
|
import { DEFAULT_SLASH_STRINGS, DEFAULT_TABLE_STRINGS } from '../core/codec/index.js';
|
|
22
22
|
/**
|
|
23
23
|
* A `Mod` chord as the writer's platform prints it: `⌘B` on Apple's, `Ctrl+B`
|
|
24
|
-
* elsewhere
|
|
25
|
-
* tooltip names the chord the keymap binds.
|
|
24
|
+
* elsewhere, and `Shift-7` as `⇧⌘7` or `Ctrl+Shift+7`. The platform test is the one
|
|
25
|
+
* `prosemirror-keymap` resolves `Mod` by, so a tooltip names the chord the keymap binds.
|
|
26
26
|
*/
|
|
27
27
|
export function chord(key, platform = typeof navigator === 'undefined' ? '' : navigator.platform) {
|
|
28
|
-
|
|
28
|
+
const shift = key.startsWith('Shift-');
|
|
29
|
+
const bare = shift ? key.slice('Shift-'.length) : key;
|
|
30
|
+
if (/Mac|iP(hone|[oa]d)/.test(platform))
|
|
31
|
+
return `${shift ? '⇧' : ''}⌘${bare}`;
|
|
32
|
+
return `Ctrl+${shift ? 'Shift+' : ''}${bare}`;
|
|
29
33
|
}
|
|
30
34
|
/** The package's English. Every key, so the merge below is total. */
|
|
31
35
|
export const DEFAULT_VISUAL_STRINGS = {
|
|
@@ -59,6 +63,8 @@ export const DEFAULT_VISUAL_STRINGS = {
|
|
|
59
63
|
formatStrikethrough: 'Strikethrough',
|
|
60
64
|
formatCode: 'Code',
|
|
61
65
|
formatLink: `Link (${chord('K')})`,
|
|
66
|
+
formatNumberedList: `Numbered list (${chord('Shift-7')})`,
|
|
67
|
+
formatBulletedList: `Bulleted list (${chord('Shift-8')})`,
|
|
62
68
|
formatAnchor: 'Anchor',
|
|
63
69
|
formatAnchorTitle: 'Anchor — an identity handle over the selection',
|
|
64
70
|
linkPlaceholder: 'https://…',
|
|
@@ -26,18 +26,17 @@ export interface FieldModel {
|
|
|
26
26
|
inline: boolean;
|
|
27
27
|
plaintext: boolean;
|
|
28
28
|
}
|
|
29
|
-
/** A `ui.group`
|
|
29
|
+
/** A panel section, a top-level `object`'s or a legacy `ui.group`'s: its label and the
|
|
30
|
+
* fields (declaration order) inside it. */
|
|
30
31
|
export interface GroupSection {
|
|
31
32
|
group: string | undefined;
|
|
32
33
|
label: string;
|
|
33
34
|
fields: FieldModel[];
|
|
34
|
-
/** The
|
|
35
|
-
*
|
|
35
|
+
/** The object the section's header names in place of a label of its own: the
|
|
36
|
+
* object a panel is, or a group section's one field where that field is an
|
|
37
|
+
* `object`. Its properties draw flat, the panel being their frame. */
|
|
36
38
|
head?: FieldModel;
|
|
37
39
|
}
|
|
38
|
-
/** `ui.layout: flat` on a typed dictionary: no frame of its own, its properties
|
|
39
|
-
* standing at the depth of the fields around it rather than a rung in. */
|
|
40
|
-
export declare function flatObject(schema: QuillFieldSchema): boolean;
|
|
41
40
|
/**
|
|
42
41
|
* One card instance projected for rendering: the schema × payload join result
|
|
43
42
|
* the VisualEditor's `$derived` produces and hands to `<Card>`. Positional
|
|
@@ -112,7 +111,7 @@ export declare function resolvedByCardIndex(resolved: Resolved | undefined): Map
|
|
|
112
111
|
export declare function ghostDefault(row: ResolvedField | undefined): unknown;
|
|
113
112
|
/** A stored `boolean` cell as the render reads it: a boolean, `true` or `false` in any
|
|
114
113
|
* case, or a number by whether it is non-zero; undefined where the render refuses the
|
|
115
|
-
* value.
|
|
114
|
+
* value. Quillmark's coercion, restated, so the suite holds it to `reader.resolve()`. */
|
|
116
115
|
export declare function renderedBoolean(value: unknown): boolean | undefined;
|
|
117
116
|
/** A ghost value's string form, or undefined for null/object (only text ghosts
|
|
118
117
|
* render a placeholder). A scalar's resolved default reads through it; a content
|
|
@@ -207,9 +206,9 @@ export declare function matrixMembers(roster: Record<string, string> | undefined
|
|
|
207
206
|
export declare function memberValue(map: Record<string, unknown> | undefined, id: string): unknown;
|
|
208
207
|
/**
|
|
209
208
|
* Whether a stored member is held: by being present. A mapping is held whatever it
|
|
210
|
-
* carries, `{}` included, and a bare scalar is the tick itself, read as
|
|
209
|
+
* carries, `{}` included, and a bare scalar is the tick itself, read as Quillmark's
|
|
211
210
|
* coercion reads it: `false`, `0`, `"false"` and `null` unheld, every other present
|
|
212
|
-
* scalar held, so a spelling
|
|
211
|
+
* scalar held, so a spelling Quillmark would refuse still draws the tick it claims.
|
|
213
212
|
*/
|
|
214
213
|
export declare function matrixHeld(stored: unknown): boolean;
|
|
215
214
|
/** A member's columns: its mapping without the reserved keys, `{}` for a bare tick or an
|
|
@@ -245,7 +244,7 @@ export declare function matrixDeclares(matrix: QuillFieldSchema, id: string): bo
|
|
|
245
244
|
/**
|
|
246
245
|
* The added items a stored map holds, sorted by id as the plate orders them: on an
|
|
247
246
|
* open matrix, each key off the roster spelled as an id whose mapping carries a
|
|
248
|
-
* `title`. Any other key off the roster is
|
|
247
|
+
* `title`. Any other key off the roster is Quillmark's refusal, drawn nowhere and
|
|
249
248
|
* ridden through.
|
|
250
249
|
*/
|
|
251
250
|
export declare function addedItems(matrix: QuillFieldSchema, map: Record<string, unknown> | undefined): MatrixMember[];
|
|
@@ -329,6 +328,19 @@ export declare function groupLabel(cardSchema: QuillCardSchema, group: string):
|
|
|
329
328
|
* the card schema, which falls back to {@link humanize} itself.
|
|
330
329
|
*/
|
|
331
330
|
export declare function groupSections(fields: FieldModel[], order: string[], labelFor: (group: string) => string): GroupSection[];
|
|
331
|
+
/**
|
|
332
|
+
* Object-panel sections (VISUAL_EDITOR §"Structure mirrors the schema"): every
|
|
333
|
+
* top-level `object` is a panel of its own, headed by the object, in declaration
|
|
334
|
+
* order; everything else stands in one unframed section above them. The section id
|
|
335
|
+
* is the field name, which a card's keys hold unique.
|
|
336
|
+
*/
|
|
337
|
+
export declare function objectSections(fields: FieldModel[]): GroupSection[];
|
|
338
|
+
/**
|
|
339
|
+
* A card's sections: its ungrouped fields' {@link objectSections}, then each legacy
|
|
340
|
+
* `ui.group` a panel in registry order, the order the loader settles the fields in.
|
|
341
|
+
* A panel named as a group is keyed off the group's id space, which `.` never joins.
|
|
342
|
+
*/
|
|
343
|
+
export declare function cardSections(cardSchema: QuillCardSchema): GroupSection[];
|
|
332
344
|
/**
|
|
333
345
|
* The group section a card's accordion opens on first mount: the first in
|
|
334
346
|
* order, or `null` when the card declares none. Ungrouped fields render outside
|