@quillmark/svelte 0.24.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/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/field.d.ts +4 -2
- 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/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 +9 -0
- package/dist/core/codec/markdown-paste.js +63 -2
- package/dist/core/codec/nesting.d.ts +5 -1
- package/dist/core/codec/nesting.js +1 -1
- package/dist/core/codec/prose.css +21 -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/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/DateField.svelte +26 -13
- package/dist/visual/FormatPopover.svelte +41 -2
- package/dist/visual/ProseValue.svelte +35 -3
- package/dist/visual/ProseValue.svelte.d.ts +7 -0
- package/dist/visual/icons/nodes.d.ts +26 -0
- package/dist/visual/icons/nodes.js +16 -0
- package/dist/visual/strings.d.ts +4 -2
- package/dist/visual/strings.js +9 -3
- package/package.json +1 -1
|
@@ -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
|
|
@@ -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);
|
|
@@ -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. */
|
|
@@ -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;
|
|
@@ -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://…',
|