@quillmark/svelte 0.20.1 → 0.21.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/atoms.js +25 -10
- package/dist/core/codec/blocks.d.ts +5 -5
- package/dist/core/codec/blocks.js +23 -6
- package/dist/core/codec/code.d.ts +3 -3
- package/dist/core/codec/code.js +51 -21
- package/dist/core/codec/field.d.ts +9 -7
- package/dist/core/codec/field.js +22 -11
- package/dist/core/codec/inputrules.d.ts +4 -0
- package/dist/core/codec/inputrules.js +43 -5
- package/dist/core/codec/link-paste.d.ts +11 -0
- package/dist/core/codec/link-paste.js +32 -0
- package/dist/core/codec/lists.d.ts +2 -2
- package/dist/core/codec/lists.js +66 -26
- package/dist/core/codec/prose.css +71 -43
- package/dist/core/codec/schema.js +55 -11
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +5 -0
- package/dist/core/codec/table-view.js +232 -103
- package/dist/core/rungs.d.ts +6 -0
- package/dist/core/rungs.js +11 -0
- package/dist/core/theme.css +24 -12
- package/dist/visual/ArrayField.svelte +168 -48
- package/dist/visual/BooleanField.svelte +12 -1
- package/dist/visual/Card.svelte +32 -20
- package/dist/visual/Card.svelte.d.ts +1 -0
- package/dist/visual/CardControls.svelte +15 -15
- package/dist/visual/DateField.svelte +93 -64
- package/dist/visual/EnumField.svelte +9 -4
- package/dist/visual/FieldHint.svelte +22 -5
- package/dist/visual/FieldLabel.svelte +3 -3
- package/dist/visual/FormatPopover.svelte +25 -9
- package/dist/visual/MatrixField.svelte +10 -7
- package/dist/visual/ObjectField.svelte +2 -4
- package/dist/visual/TipsCard.svelte +2 -5
- package/dist/visual/VisualEditorInner.svelte +108 -26
- package/dist/visual/controls.css +2 -1
- package/dist/visual/icons/Icon.svelte +3 -2
- package/dist/visual/icons/Icon.svelte.d.ts +3 -2
- package/dist/visual/icons/nodes.d.ts +25 -0
- package/dist/visual/icons/nodes.js +8 -0
- package/dist/visual/motion.d.ts +25 -3
- package/dist/visual/motion.js +58 -9
- package/dist/visual/strings.d.ts +6 -5
- package/dist/visual/strings.js +3 -3
- package/package.json +2 -2
package/dist/core/codec/lists.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { liftListItem, sinkListItem, splitListItem } from 'prosemirror-schema-list';
|
|
2
2
|
import { EditorState, Selection, TextSelection } from 'prosemirror-state';
|
|
3
|
-
import { chainCommands, joinTextblockBackward } from 'prosemirror-commands';
|
|
3
|
+
import { chainCommands, joinTextblockBackward, joinTextblockForward } from 'prosemirror-commands';
|
|
4
4
|
/** The caret's enclosing `list_item` when it sits at the very start of that item's
|
|
5
5
|
* first block: the position both structural keys branch on. `null` otherwise
|
|
6
6
|
* (mid-text, a non-first block of a multi-paragraph item, a non-empty selection),
|
|
@@ -72,41 +72,68 @@ function paragraphAboveList(itemType, paragraph) {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
* Backspace at the start of
|
|
76
|
-
*
|
|
75
|
+
* Backspace at the start of an item → lift it: one level out for a nested item, out of
|
|
76
|
+
* the list to a paragraph at the top, whichever item it is. The first press takes off
|
|
77
|
+
* the item's own shape and joins nothing; the press after is the join
|
|
78
|
+
* ({@link joinOntoList}).
|
|
79
|
+
*
|
|
80
|
+
* Lifting a later item out splits its list, and an ordered list's items past the cut
|
|
81
|
+
* keep their numbers: the list after what the item held starts where they were, where a
|
|
82
|
+
* fresh `ordered_list` would count from 1 again.
|
|
77
83
|
*/
|
|
78
|
-
function
|
|
84
|
+
function liftAtItemStart(itemType) {
|
|
79
85
|
return (state, dispatch) => {
|
|
80
86
|
const at = atItemStart(state, itemType);
|
|
81
|
-
if (!at
|
|
87
|
+
if (!at)
|
|
82
88
|
return false;
|
|
83
|
-
|
|
89
|
+
const list = state.doc.nodeAt(at.listPos);
|
|
90
|
+
const cut = list?.type.name === 'ordered_list' && !at.nested && at.itemIndex < list.childCount - 1;
|
|
91
|
+
const listEnd = at.listPos + (list?.nodeSize ?? 0);
|
|
92
|
+
return liftListItem(itemType)(state, dispatch &&
|
|
93
|
+
((tr) => {
|
|
94
|
+
if (cut)
|
|
95
|
+
renumberEndingAt(tr, tr.mapping.map(listEnd, -1), list.attrs.start + at.itemIndex + 1);
|
|
96
|
+
dispatch(tr);
|
|
97
|
+
}));
|
|
84
98
|
};
|
|
85
99
|
}
|
|
100
|
+
/** Start the ordered list ending at `end` at `start`: the items a lift cut off, which
|
|
101
|
+
* end where the whole list did, past whatever the lifted item held. */
|
|
102
|
+
function renumberEndingAt(tr, end, start) {
|
|
103
|
+
const list = tr.doc.resolve(end).nodeBefore;
|
|
104
|
+
if (list?.type.name === 'ordered_list' && list.attrs.start !== start)
|
|
105
|
+
tr.setNodeMarkup(end - list.nodeSize, undefined, { ...list.attrs, start });
|
|
106
|
+
}
|
|
86
107
|
/**
|
|
87
|
-
* Backspace at the
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* `list_item(paragraph, paragraph)`: the item's marker gone while its text stays
|
|
92
|
-
* on its own line, which the reference quill typesets as an unnumbered
|
|
93
|
-
* continuation paragraph.
|
|
108
|
+
* Backspace at the head of a block that stands right after a list → join its text into
|
|
109
|
+
* the list's last line, as two paragraphs join. The base keymap's answer there wraps the
|
|
110
|
+
* block into a fresh item instead, which undoes the lift that just took it out of the
|
|
111
|
+
* list ({@link liftAtItemStart}) and leaves the key toggling.
|
|
94
112
|
*
|
|
95
|
-
*
|
|
96
|
-
* and the join
|
|
97
|
-
*
|
|
98
|
-
*
|
|
113
|
+
* Between a fence and prose the text does not join: a fence is not a line to put another
|
|
114
|
+
* line on, and the join would retype one side's whole text as the other's (`code.ts`,
|
|
115
|
+
* which refuses the same join between siblings). The block moves into the list's last
|
|
116
|
+
* item whole instead, a block of that item under its first.
|
|
99
117
|
*/
|
|
100
|
-
function
|
|
118
|
+
function joinOntoList(itemType) {
|
|
101
119
|
return (state, dispatch, view) => {
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
120
|
+
const { $from, empty } = state.selection;
|
|
121
|
+
if (!empty || !$from.parent.isTextblock || $from.parentOffset !== 0)
|
|
104
122
|
return false;
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
123
|
+
const before = state.doc.resolve($from.before()).nodeBefore;
|
|
124
|
+
if (before?.firstChild?.type !== itemType)
|
|
107
125
|
return false;
|
|
126
|
+
const into = lastBlockIn(before);
|
|
127
|
+
if (!into)
|
|
128
|
+
return false;
|
|
129
|
+
if (!!into.type.spec.code === !!$from.parent.type.spec.code)
|
|
130
|
+
return joinTextblockBackward(state, dispatch, view);
|
|
131
|
+
if (dispatch) {
|
|
132
|
+
const at = $from.before() - 2;
|
|
133
|
+
const tr = state.tr.delete($from.before(), $from.after()).insert(at, $from.parent);
|
|
134
|
+
dispatch(tr.setSelection(TextSelection.create(tr.doc, at + 1)).scrollIntoView());
|
|
108
135
|
}
|
|
109
|
-
return
|
|
136
|
+
return true;
|
|
110
137
|
};
|
|
111
138
|
}
|
|
112
139
|
/** Whether the caret sits inside a `list_item` at any depth: a quote's paragraph
|
|
@@ -128,6 +155,10 @@ function inItem($pos, itemType) {
|
|
|
128
155
|
* The reflection carries the edit and not the caret, which stays where the key was
|
|
129
156
|
* pressed: a lift moves the item below, and Delete does not follow it.
|
|
130
157
|
*
|
|
158
|
+
* An empty item is the exception: its line is the whole of what Delete edits, so the
|
|
159
|
+
* item below moves up into it rather than being lifted, and the empty item is spent. A
|
|
160
|
+
* fence below takes no prose line (`joinOntoList`), so there the empty line goes alone.
|
|
161
|
+
*
|
|
131
162
|
* Declining wherever the chain does is what keeps this to the list's own seams: a
|
|
132
163
|
* boundary inside one item, and the list's outer edge, stay the base keymap's, where
|
|
133
164
|
* the two keys already answer alike.
|
|
@@ -142,6 +173,15 @@ function backspaceBelow(itemType, press) {
|
|
|
142
173
|
const below = Selection.near(state.doc.resolve($from.after()), 1);
|
|
143
174
|
if (below.from <= $from.pos)
|
|
144
175
|
return false;
|
|
176
|
+
if ($from.parent.content.size === 0) {
|
|
177
|
+
if (!!below.$from.parent.type.spec.code === !!$from.parent.type.spec.code)
|
|
178
|
+
return joinTextblockForward(state, dispatch);
|
|
179
|
+
if (dispatch) {
|
|
180
|
+
const tr = state.tr.deleteRange($from.before(), $from.after());
|
|
181
|
+
dispatch(tr.setSelection(Selection.near(tr.doc.resolve(tr.mapping.map(below.from)), 1)));
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
145
185
|
const pressed = $from.pos;
|
|
146
186
|
const keepCaret = (tr) => dispatch?.(tr.setSelection(TextSelection.create(tr.doc, tr.mapping.map(pressed))));
|
|
147
187
|
return press(EditorState.create({ doc: state.doc, selection: below }), dispatch && keepCaret);
|
|
@@ -163,8 +203,8 @@ function backspaceBelow(itemType, press) {
|
|
|
163
203
|
* `splitListItem`, then `liftListItem`. The middle link carries two behaviors of
|
|
164
204
|
* its own: it splits a non-empty item, and on an empty item it either splits the
|
|
165
205
|
* wrapping item (a nested empty item, so Enter lifts exactly one level) or bails
|
|
166
|
-
* so the third link lifts a top-level empty item out to a paragraph. `Backspace`
|
|
167
|
-
*
|
|
206
|
+
* so the third link lifts a top-level empty item out to a paragraph. `Backspace` lifts
|
|
207
|
+
* an item at its start, and joins a block onto the list it stands after. `Delete` is
|
|
168
208
|
* that same chain at the seam below rather than a second table of cases beside it, so
|
|
169
209
|
* the two keys cannot come to differ over a seam this link reaches. An empty line is
|
|
170
210
|
* not one: the block link takes it ahead of this chain (`blocks.ts`), the caret's own
|
|
@@ -175,7 +215,7 @@ export function listKeymap(schema) {
|
|
|
175
215
|
const paragraph = schema.nodes.paragraph;
|
|
176
216
|
if (!itemType || !paragraph)
|
|
177
217
|
return {};
|
|
178
|
-
const backspace = chainCommands(
|
|
218
|
+
const backspace = chainCommands(liftAtItemStart(itemType), joinOntoList(itemType));
|
|
179
219
|
return {
|
|
180
220
|
Tab: sinkListItem(itemType),
|
|
181
221
|
'Shift-Tab': liftListItem(itemType),
|
|
@@ -29,6 +29,14 @@ li.ProseMirror-selectednode::after {
|
|
|
29
29
|
border: var(--_qm-ring);
|
|
30
30
|
left: calc(var(--_qm-space-4) * -1);
|
|
31
31
|
}
|
|
32
|
+
/* A node selection is drawn while its leaf holds the focus, as a text selection is: a
|
|
33
|
+
ring left behind on a field the focus has moved off names a subject no key reaches. */
|
|
34
|
+
.ProseMirror:not(:focus-within) .ProseMirror-selectednode {
|
|
35
|
+
outline: none;
|
|
36
|
+
}
|
|
37
|
+
.ProseMirror:not(:focus-within) li.ProseMirror-selectednode::after {
|
|
38
|
+
border: none;
|
|
39
|
+
}
|
|
32
40
|
|
|
33
41
|
/* The gap cursor (`prosemirror-gapcursor`, imported beside this file): a caret
|
|
34
42
|
between two blocks, where the document holds no text position for an ordinary one.
|
|
@@ -227,9 +235,12 @@ li.ProseMirror-selectednode::after {
|
|
|
227
235
|
empty line the caret holds. Only while the leaf has the focus: the decoration follows
|
|
228
236
|
the selection, which an unfocused leaf keeps. Upright, being chrome about the key
|
|
229
237
|
rather than an invitation in the document's voice, and never over a ghost, which is
|
|
230
|
-
the leaf's own word for the same empty line.
|
|
238
|
+
the leaf's own word for the same empty line. It comes up once the caret has rested
|
|
239
|
+
there, a new empty line starting the wait over. */
|
|
231
240
|
.ProseMirror-focused .qm-slash-hint:not(.qm-prose-placeholder)[data-slash-hint]::after {
|
|
232
241
|
content: attr(data-slash-hint);
|
|
242
|
+
animation: qm-slash-hint-in var(--_qm-reveal) both;
|
|
243
|
+
animation-delay: var(--_qm-hint-wait);
|
|
233
244
|
display: block;
|
|
234
245
|
margin-top: -1lh;
|
|
235
246
|
white-space: nowrap;
|
|
@@ -239,6 +250,12 @@ li.ProseMirror-selectednode::after {
|
|
|
239
250
|
pointer-events: none;
|
|
240
251
|
}
|
|
241
252
|
|
|
253
|
+
@keyframes qm-slash-hint-in {
|
|
254
|
+
from {
|
|
255
|
+
opacity: 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
242
259
|
/* ── The table island ────────────────────────────────────────────────────────
|
|
243
260
|
|
|
244
261
|
The one construct in this file with chrome in it. The rest of the stylesheet
|
|
@@ -313,19 +330,16 @@ li.ProseMirror-selectednode::after {
|
|
|
313
330
|
.qm-table {
|
|
314
331
|
border-collapse: separate;
|
|
315
332
|
border-spacing: 0;
|
|
316
|
-
border: var(--_qm-border-width) solid var(--_qm-
|
|
317
|
-
border-radius: var(--_qm-radius
|
|
333
|
+
border: var(--_qm-border-width) solid var(--_qm-rule);
|
|
334
|
+
border-radius: var(--_qm-radius);
|
|
318
335
|
}
|
|
319
|
-
/*
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
rather than as a stack of boxes. `separate` with zero spacing rather than
|
|
323
|
-
`collapse`: a collapsed border cannot be rounded, and the two tiers would fight
|
|
324
|
-
over which one wins an edge. */
|
|
336
|
+
/* One rule at one strength, the frame's and every line between cells, so the grid
|
|
337
|
+
reads at the weight of the type it holds. `separate` with zero spacing rather than
|
|
338
|
+
`collapse`: a collapsed border cannot be rounded. */
|
|
325
339
|
.qm-table-cell {
|
|
326
340
|
position: relative;
|
|
327
|
-
border-right: var(--_qm-border-width) solid var(--_qm-
|
|
328
|
-
border-bottom: var(--_qm-border-width) solid var(--_qm-
|
|
341
|
+
border-right: var(--_qm-border-width) solid var(--_qm-rule);
|
|
342
|
+
border-bottom: var(--_qm-border-width) solid var(--_qm-rule);
|
|
329
343
|
padding: var(--_qm-space) var(--_qm-space-2);
|
|
330
344
|
vertical-align: top;
|
|
331
345
|
font-weight: inherit;
|
|
@@ -339,13 +353,29 @@ li.ProseMirror-selectednode::after {
|
|
|
339
353
|
}
|
|
340
354
|
/* The header row is the one weight distinction the island draws: `header` is a
|
|
341
355
|
separate field in the model rather than a flag on a row, so the surface says so.
|
|
342
|
-
It takes a tone with it,
|
|
343
|
-
|
|
344
|
-
rather than a shadow. The rest of a table's type is paper's. */
|
|
356
|
+
It takes a tone with it, the band rung, a step toward the ink at both poles. The
|
|
357
|
+
rest of a table's type is paper's. */
|
|
345
358
|
.qm-table-header-row .qm-table-cell {
|
|
346
359
|
font-weight: var(--_qm-weight-strong);
|
|
347
|
-
background: var(--_qm-surface-
|
|
348
|
-
|
|
360
|
+
background: var(--_qm-surface-band);
|
|
361
|
+
}
|
|
362
|
+
/* The corner cells take the frame's corner, inside its stroke, so a header's band or a
|
|
363
|
+
selection's wash keeps to the rounded edge rather than painting a square one over
|
|
364
|
+
it. The first row is the colgroup's next sibling's, whichever section holds it. */
|
|
365
|
+
.qm-table > colgroup + * > tr:first-child > .qm-table-cell:first-child {
|
|
366
|
+
border-start-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
367
|
+
}
|
|
368
|
+
.qm-table > colgroup + * > tr:first-child > .qm-table-cell:last-child {
|
|
369
|
+
border-start-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
370
|
+
}
|
|
371
|
+
.qm-table
|
|
372
|
+
> :is(:last-child, thead:has(+ tbody:empty))
|
|
373
|
+
> tr:last-child
|
|
374
|
+
> .qm-table-cell:first-child {
|
|
375
|
+
border-end-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
376
|
+
}
|
|
377
|
+
.qm-table > :is(:last-child, thead:has(+ tbody:empty)) > tr:last-child > .qm-table-cell:last-child {
|
|
378
|
+
border-end-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
349
379
|
}
|
|
350
380
|
/* A cell holds a whole nested view. Its measure floor keeps an empty table from
|
|
351
381
|
collapsing to a row of hairlines with nothing to aim at, and the caret is the
|
|
@@ -400,20 +430,16 @@ li.ProseMirror-selectednode::after {
|
|
|
400
430
|
color: var(--_qm-ink);
|
|
401
431
|
cursor: pointer;
|
|
402
432
|
}
|
|
403
|
-
/* The
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
One tone, not a hover ladder: the control is drawn only while it is the one the
|
|
408
|
-
pointer is on or the one a gesture is about, so a resting tone under that would be a
|
|
409
|
-
rung nothing ever reaches. The selected grip's tint below is the one exception, and it
|
|
410
|
-
says which line the next command acts on rather than where the pointer is. */
|
|
433
|
+
/* The pill every one of them draws, a rhythm rung thick where the target is the tap
|
|
434
|
+
floor. Stated once, so a grip and an add bar cannot drift into two shapes. The selected
|
|
435
|
+
grip's tint below says which line the next command acts on rather than where the
|
|
436
|
+
pointer is. */
|
|
411
437
|
:is(.qm-table-grip-bar, .qm-table-add-bar) {
|
|
412
438
|
display: flex;
|
|
413
439
|
align-items: center;
|
|
414
440
|
justify-content: center;
|
|
415
441
|
border-radius: var(--_qm-radius-pill);
|
|
416
|
-
background: var(--_qm-
|
|
442
|
+
background: var(--_qm-surface-band);
|
|
417
443
|
transition:
|
|
418
444
|
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
419
445
|
color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
@@ -424,32 +450,34 @@ li.ProseMirror-selectednode::after {
|
|
|
424
450
|
width: var(--_qm-space-3);
|
|
425
451
|
height: var(--_qm-space-3);
|
|
426
452
|
}
|
|
427
|
-
/*
|
|
428
|
-
|
|
429
|
-
is the one thing the rule above cannot share with them: a column's grip is as wide as
|
|
430
|
-
its column, and the bar that adds a column is as tall as the table. */
|
|
431
|
-
.qm-table-grip[data-axis='column'] .qm-table-grip-bar,
|
|
453
|
+
/* An add bar runs across the axis it appends to, the length of the table; a grip is a
|
|
454
|
+
short pill centred on the line it names, the tap floor long. */
|
|
432
455
|
.qm-table-add[data-axis='row'] .qm-table-add-bar {
|
|
433
456
|
width: 100%;
|
|
434
457
|
height: var(--_qm-space-3);
|
|
435
458
|
}
|
|
436
|
-
.qm-table-grip[data-axis='row'] .qm-table-grip-bar,
|
|
437
459
|
.qm-table-add[data-axis='column'] .qm-table-add-bar {
|
|
438
460
|
width: var(--_qm-space-3);
|
|
439
461
|
height: 100%;
|
|
440
462
|
}
|
|
463
|
+
.qm-table-grip[data-axis='column'] .qm-table-grip-bar {
|
|
464
|
+
width: var(--_qm-tap-min);
|
|
465
|
+
height: var(--_qm-space-3);
|
|
466
|
+
}
|
|
467
|
+
.qm-table-grip[data-axis='row'] .qm-table-grip-bar {
|
|
468
|
+
width: var(--_qm-space-3);
|
|
469
|
+
height: var(--_qm-tap-min);
|
|
470
|
+
}
|
|
441
471
|
|
|
442
|
-
/* One grip per line
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
you can pick up has everywhere else — so the control reads as draggable before it is
|
|
446
|
-
dragged.
|
|
472
|
+
/* One grip per line. The pill carries the grip's own marks at the label ink — two rows
|
|
473
|
+
of three, the shape a thing you can pick up has everywhere else — so the control reads
|
|
474
|
+
as draggable before it is dragged.
|
|
447
475
|
|
|
448
|
-
The target is the band and the
|
|
449
|
-
axis and the tap floor on the other
|
|
450
|
-
|
|
451
|
-
the pointer has to find. */
|
|
476
|
+
The target is the band and the pill is drawn inside it: a grip spans its line on one
|
|
477
|
+
axis and the tap floor on the other. A grip sized to its own pill would be a target
|
|
478
|
+
the size of the mark on it, on a control the pointer has to find. */
|
|
452
479
|
.qm-table-grip {
|
|
480
|
+
color: var(--_qm-ink-label);
|
|
453
481
|
/* The pointer owns this gesture, not the page's scroll: a grip drags. */
|
|
454
482
|
touch-action: none;
|
|
455
483
|
}
|
|
@@ -490,8 +518,8 @@ li.ProseMirror-selectednode::after {
|
|
|
490
518
|
/* The reveal is the control's own (CODEC §"The table island"): one comes up while the
|
|
491
519
|
pointer is on that control, while it holds the focus, or while it is what a gesture is
|
|
492
520
|
about — the selected line's grip, and the lifted one, which is drawn through a drag the
|
|
493
|
-
pointer has travelled off.
|
|
494
|
-
|
|
521
|
+
pointer has travelled off. A cell under the pointer draws its row's and its column's
|
|
522
|
+
grips (`table-view.ts` §`onHover`), one pill on each axis and none anywhere else.
|
|
495
523
|
|
|
496
524
|
Opacity rather than absence, so every control keeps its box and is hit-tested wherever
|
|
497
525
|
it sits. A grip's reach is the pointer's alone and it holds no focus, so it carries no
|
|
@@ -504,7 +532,7 @@ li.ProseMirror-selectednode::after {
|
|
|
504
532
|
transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
505
533
|
}
|
|
506
534
|
.qm-table-add:is(:hover, :focus-visible),
|
|
507
|
-
.qm-table-grip:is(:hover, [aria-pressed='true'], .qm-table-lifted) {
|
|
535
|
+
.qm-table-grip:is([data-near], :hover, [aria-pressed='true'], .qm-table-lifted) {
|
|
508
536
|
opacity: 1;
|
|
509
537
|
}
|
|
510
538
|
|
|
@@ -14,19 +14,64 @@
|
|
|
14
14
|
// paste inside one body run the whole document through them (CODEC §"Markdown at the
|
|
15
15
|
// edges"), so every attribute written here is read back here. Foreign HTML spells none
|
|
16
16
|
// of this package's `data-qm-*` names, so what a paste takes off the web is unchanged;
|
|
17
|
-
// the
|
|
18
|
-
// the block leaf's clipboard parser's (`table-paste.ts`).
|
|
17
|
+
// the rules here that widen that door on purpose are the fence's and a span's styled
|
|
18
|
+
// marks, a `<table>` being the block leaf's clipboard parser's (`table-paste.ts`).
|
|
19
19
|
import { Schema } from 'prosemirror-model';
|
|
20
20
|
import { islandBlockSpec, islandInlineSpec } from './islands.js';
|
|
21
21
|
import { rendersHref, storableUrl } from './urls.js';
|
|
22
|
+
// ── Marks spelled as style ──────────────────────────────────────────────────
|
|
23
|
+
// A word processor writes formatting as a span's inline style rather than as a tag:
|
|
24
|
+
// Google Docs spells all four marks below that way, and wraps a whole copy in a `<b>`
|
|
25
|
+
// stating a regular weight. Read off a `<span>` alone, since the same style on a cell or
|
|
26
|
+
// a heading is that block's face rather than a run's. An underline inside a link is the
|
|
27
|
+
// link's own: Docs underlines every one, and Chrome inlines the computed decoration on a
|
|
28
|
+
// copy off any page.
|
|
29
|
+
/** Whether `el`'s inline style states a bold weight. */
|
|
30
|
+
const bold = (el) => /^(?:bold(?:er)?|[6-9]\d\d)$/.test(el.style.fontWeight);
|
|
31
|
+
/** The text decoration `el` states, as a shorthand or a longhand. */
|
|
32
|
+
const decoration = (el) => `${el.style.textDecoration} ${el.style.textDecorationLine}`;
|
|
33
|
+
/** A mark a `<span>` carries where `styled` holds of it. Not consuming, so every mark a
|
|
34
|
+
* span states matches, and so do the span's own rules after them. */
|
|
35
|
+
const styledSpan = (styled) => ({
|
|
36
|
+
tag: 'span',
|
|
37
|
+
consuming: false,
|
|
38
|
+
getAttrs: (el) => styled(el) && null
|
|
39
|
+
});
|
|
22
40
|
// ── Marks (the block and inline schemas share them; plaintext declares none) ─
|
|
23
41
|
const marks = {
|
|
24
42
|
// Order matters: it fixes mark-set sort order and parse precedence. `link`
|
|
25
43
|
// last so it wraps outermost.
|
|
26
|
-
strong: {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
strong: {
|
|
45
|
+
parseDOM: [
|
|
46
|
+
{ tag: 'strong' },
|
|
47
|
+
{ tag: 'b', getAttrs: (el) => (!el.style.fontWeight || bold(el)) && null },
|
|
48
|
+
styledSpan(bold)
|
|
49
|
+
],
|
|
50
|
+
toDOM: () => ['strong', 0]
|
|
51
|
+
},
|
|
52
|
+
em: {
|
|
53
|
+
parseDOM: [
|
|
54
|
+
{ tag: 'em' },
|
|
55
|
+
{ tag: 'i' },
|
|
56
|
+
styledSpan((el) => /^(?:italic|oblique)\b/.test(el.style.fontStyle))
|
|
57
|
+
],
|
|
58
|
+
toDOM: () => ['em', 0]
|
|
59
|
+
},
|
|
60
|
+
underline: {
|
|
61
|
+
parseDOM: [
|
|
62
|
+
{ tag: 'u' },
|
|
63
|
+
styledSpan((el) => /\bunderline\b/.test(decoration(el)) && !el.closest('a'))
|
|
64
|
+
],
|
|
65
|
+
toDOM: () => ['u', 0]
|
|
66
|
+
},
|
|
67
|
+
strike: {
|
|
68
|
+
parseDOM: [
|
|
69
|
+
{ tag: 's' },
|
|
70
|
+
{ tag: 'del' },
|
|
71
|
+
styledSpan((el) => /\bline-through\b/.test(decoration(el)))
|
|
72
|
+
],
|
|
73
|
+
toDOM: () => ['s', 0]
|
|
74
|
+
},
|
|
30
75
|
code: { parseDOM: [{ tag: 'code' }], toDOM: () => ['code', 0] },
|
|
31
76
|
link: {
|
|
32
77
|
attrs: { href: { default: '' } },
|
|
@@ -50,11 +95,10 @@ const marks = {
|
|
|
50
95
|
}
|
|
51
96
|
};
|
|
52
97
|
// ── The fence's language ────────────────────────────────────────────────────
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// that page stated, one copied inside the body keeps its own.
|
|
98
|
+
// Minted by the fence shorthand, which fires on Enter with the language typed after the
|
|
99
|
+
// backticks (`inputrules.ts`), and by a paste, which this reads: a fence copied off a
|
|
100
|
+
// highlighted page arrives carrying the language that page stated, one copied inside
|
|
101
|
+
// the body keeps its own.
|
|
58
102
|
/** A `language-x` / `lang-x` token in a class list. */
|
|
59
103
|
const LANG_CLASS = /(?:^|\s)lang(?:uage)?-(\S+)/;
|
|
60
104
|
/** The language a `<pre>` states: `data-lang`, which is what `toDOM` writes, else the
|
package/dist/core/codec/slash.js
CHANGED
|
Binary file
|
|
@@ -57,6 +57,11 @@ export interface TableViewDeps {
|
|
|
57
57
|
* which is PM's 5px default. */
|
|
58
58
|
clearance: number | undefined;
|
|
59
59
|
}
|
|
60
|
+
/** The table island mounted at `pos` in `view`, which a caret can step into; `undefined`
|
|
61
|
+
* for any other node, or where no view is mounted. */
|
|
62
|
+
export declare function tableAt(view: EditorView, pos: number): {
|
|
63
|
+
enter(side: -1 | 1): void;
|
|
64
|
+
} | undefined;
|
|
60
65
|
/** The `island_block` node view: a table island's editing surface, and the literal
|
|
61
66
|
* placeholder for every other island type. */
|
|
62
67
|
export declare function tableNodeView(deps: TableViewDeps): NodeViewConstructor;
|