@quillmark/svelte 0.26.2 → 0.28.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/NOTICE +4 -4
- package/README.md +26 -8
- package/THEMING.md +40 -4
- package/dist/core/address.d.ts +35 -64
- package/dist/core/address.js +35 -55
- package/dist/core/bloom.d.ts +4 -10
- package/dist/core/bloom.js +14 -27
- package/dist/core/codec/blocks.d.ts +41 -43
- package/dist/core/codec/blocks.js +97 -130
- package/dist/core/codec/code.d.ts +7 -10
- package/dist/core/codec/code.js +37 -46
- package/dist/core/codec/decode.d.ts +19 -23
- package/dist/core/codec/decode.js +76 -111
- package/dist/core/codec/encode.d.ts +11 -15
- package/dist/core/codec/encode.js +60 -67
- package/dist/core/codec/field.d.ts +62 -95
- package/dist/core/codec/field.js +107 -179
- package/dist/core/codec/islands.d.ts +33 -41
- package/dist/core/codec/islands.js +76 -86
- package/dist/core/codec/lists.d.ts +17 -26
- package/dist/core/codec/lists.js +117 -157
- package/dist/core/codec/marks.d.ts +4 -3
- package/dist/core/codec/marks.js +8 -3
- package/dist/core/codec/prose.css +84 -46
- package/dist/core/codec/schema.d.ts +18 -23
- package/dist/core/codec/schema.js +84 -76
- package/dist/core/codec/slash.d.ts +42 -48
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +4 -3
- package/dist/core/codec/table-copy.js +25 -7
- package/dist/core/codec/table-view.d.ts +12 -12
- package/dist/core/codec/table-view.js +460 -383
- package/dist/core/codec/table.d.ts +94 -61
- package/dist/core/codec/table.js +331 -83
- package/dist/core/codec/tasks.d.ts +6 -0
- package/dist/core/codec/tasks.js +30 -3
- package/dist/core/errors.d.ts +15 -30
- package/dist/core/errors.js +2 -6
- package/dist/core/index.js +7 -12
- package/dist/core/lifecycle.d.ts +7 -16
- package/dist/core/lifecycle.js +10 -21
- package/dist/core/rebind.svelte.d.ts +10 -20
- package/dist/core/rebind.svelte.js +15 -27
- package/dist/core/rungs.d.ts +14 -11
- package/dist/core/rungs.js +17 -19
- package/dist/core/teardown.d.ts +4 -6
- package/dist/core/teardown.js +7 -10
- package/dist/core/theme.css +74 -54
- package/dist/index.js +3 -8
- package/dist/preset/app.css +71 -0
- package/dist/preset/index.css +8 -7
- package/dist/preset/recipes.css +19 -164
- package/dist/preset/scale.css +28 -18
- package/dist/preset/shell.css +157 -0
- package/dist/preview/Preview.svelte +8 -0
- package/dist/preview/bridge.d.ts +18 -22
- package/dist/preview/bridge.js +29 -58
- package/dist/preview/controller.d.ts +21 -29
- package/dist/preview/controller.js +14 -39
- package/dist/preview/geometry.d.ts +13 -34
- package/dist/preview/geometry.js +13 -34
- package/dist/preview/index.js +4 -16
- package/dist/preview/paint.d.ts +3 -3
- package/dist/preview/paint.js +28 -68
- package/dist/preview/strings.d.ts +1 -1
- package/dist/preview/strings.js +2 -14
- package/dist/preview/viewport.js +20 -15
- package/dist/preview/zoom.d.ts +21 -22
- package/dist/preview/zoom.js +53 -30
- package/dist/visual/ArrayField.svelte +77 -16
- package/dist/visual/BooleanField.svelte +28 -15
- package/dist/visual/Card.svelte +66 -51
- package/dist/visual/CardControls.svelte +5 -11
- package/dist/visual/DateField.svelte +20 -4
- package/dist/visual/EnumField.svelte +3 -0
- package/dist/visual/FieldHint.svelte +2 -1
- package/dist/visual/FormatPopover.svelte +82 -15
- package/dist/visual/FormatPopover.svelte.d.ts +4 -0
- package/dist/visual/MatrixAdd.svelte +30 -12
- package/dist/visual/MatrixField.svelte +24 -16
- package/dist/visual/NumberField.svelte +7 -0
- package/dist/visual/ObjectField.svelte +4 -4
- package/dist/visual/ProseField.svelte +0 -5
- package/dist/visual/TipsCard.svelte +11 -10
- package/dist/visual/VariantField.svelte +3 -1
- package/dist/visual/VisualEditorInner.svelte +19 -3
- package/dist/visual/controls.css +7 -2
- package/dist/visual/fonts.d.ts +18 -0
- package/dist/visual/fonts.js +59 -0
- package/dist/visual/icons/nodes.d.ts +0 -5
- package/dist/visual/icons/nodes.js +0 -4
- package/dist/visual/props.d.ts +14 -1
- package/dist/visual/strings.d.ts +7 -0
- package/dist/visual/strings.js +5 -2
- package/dist/visual/structure.d.ts +43 -87
- package/dist/visual/structure.js +42 -83
- package/package.json +2 -2
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
outline: var(--_qm-ring);
|
|
24
24
|
}
|
|
25
25
|
/* Upstream's `-32px` is measured against the UA's 40px list indent, which the list
|
|
26
|
-
rule below replaces
|
|
27
|
-
|
|
26
|
+
rule below replaces. The offset is that indent, so a selected item's outline takes in
|
|
27
|
+
its marker and stands on the list's own edge rather than crossing a `10.` or hanging
|
|
28
|
+
into the margin. */
|
|
28
29
|
li.ProseMirror-selectednode::after {
|
|
29
30
|
border: var(--_qm-ring);
|
|
30
|
-
left: calc(var(--_qm-
|
|
31
|
+
left: calc(-1 * (var(--_qm-marker-digits) + var(--_qm-nest)));
|
|
31
32
|
}
|
|
32
33
|
/* A node selection is drawn while its leaf holds the focus, as a text selection is: a
|
|
33
34
|
ring left behind on a field the focus has moved off names a subject no key reaches. */
|
|
@@ -57,29 +58,17 @@ li.ProseMirror-selectednode::after {
|
|
|
57
58
|
/* One rhythm between sibling blocks, in place of the UA's collapsed pair. Scoped by
|
|
58
59
|
parent rather than by tag, which is what keeps it true as the schema grows: the
|
|
59
60
|
containers below are every node whose children are blocks (`schema.ts`), whatever
|
|
60
|
-
kinds exist
|
|
61
|
-
so
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
has the gap below it that the next item draws above, where a tight run would read it
|
|
65
|
-
as the head of the next item.
|
|
61
|
+
kinds exist. A list is the exception: the whole list is one block among its
|
|
62
|
+
siblings, so it stands off them by the gap and its insides are tight: items, an
|
|
63
|
+
item's unnumbered lines, a task item's body and a sub-list all sit flush, told
|
|
64
|
+
apart by marker and indent rather than by space.
|
|
66
65
|
|
|
67
66
|
A line box is ~22px at the paper size and the body leading, so the rung is a
|
|
68
67
|
little over half a line: enough that a break reads as a break, short of the full
|
|
69
68
|
blank line that doubles the leading and makes a short document read as a list of
|
|
70
69
|
fragments. */
|
|
71
70
|
.ProseMirror > * + *,
|
|
72
|
-
.ProseMirror
|
|
73
|
-
:is(
|
|
74
|
-
blockquote,
|
|
75
|
-
ul,
|
|
76
|
-
ol,
|
|
77
|
-
li:not([data-qm-checked]),
|
|
78
|
-
[data-qm-task-body],
|
|
79
|
-
[data-qm-unknown-container]
|
|
80
|
-
)
|
|
81
|
-
> *
|
|
82
|
-
+ * {
|
|
71
|
+
.ProseMirror :is(blockquote, [data-qm-unknown-container]) > * + * {
|
|
83
72
|
margin-top: var(--_qm-space-3);
|
|
84
73
|
}
|
|
85
74
|
|
|
@@ -203,7 +192,9 @@ li.ProseMirror-selectednode::after {
|
|
|
203
192
|
}
|
|
204
193
|
|
|
205
194
|
/* The UA's 40px list indent is the one box the reset above does not take (it is
|
|
206
|
-
padding, not margin), and it is a length nothing chose.
|
|
195
|
+
padding, not margin), and it is a length nothing chose. The indent is room for a
|
|
196
|
+
marker's two digits and the nesting step past them, so a marker, `10.` included,
|
|
197
|
+
hangs into its own list's indent and never past the leaf's edge.
|
|
207
198
|
|
|
208
199
|
The markers are the UA's, reclaimed rather than inherited. `disc` and `decimal`
|
|
209
200
|
are right and this restates neither: a host reset (Tailwind's preflight sets
|
|
@@ -217,7 +208,7 @@ li.ProseMirror-selectednode::after {
|
|
|
217
208
|
that takes those ever turns up: the positive half states what it leaves to the
|
|
218
209
|
UA, and what a reset takes is what has to be asked back. */
|
|
219
210
|
.ProseMirror :is(ul, ol) {
|
|
220
|
-
padding-left: var(--_qm-
|
|
211
|
+
padding-left: calc(var(--_qm-marker-digits) + var(--_qm-nest));
|
|
221
212
|
list-style: revert;
|
|
222
213
|
}
|
|
223
214
|
|
|
@@ -240,7 +231,7 @@ li.ProseMirror-selectednode::after {
|
|
|
240
231
|
width: var(--_qm-glyph-control);
|
|
241
232
|
height: var(--_qm-glyph-control);
|
|
242
233
|
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
243
|
-
border-radius:
|
|
234
|
+
border-radius: var(--_qm-radius-inner);
|
|
244
235
|
background: var(--_qm-surface-well);
|
|
245
236
|
cursor: pointer;
|
|
246
237
|
transition:
|
|
@@ -277,10 +268,8 @@ li.ProseMirror-selectednode::after {
|
|
|
277
268
|
/* Six levels, one weight, no size step. The quill reads no level at all, so a size
|
|
278
269
|
ramp here would be the editor predicting a hierarchy the paper does not set; the
|
|
279
270
|
level stays in the model, which is where the codec round-trips it. It is also all
|
|
280
|
-
the ramp could carry: this leaf renders at `--_qm-text-paper
|
|
281
|
-
|
|
282
|
-
reading. Six levels do not fit in the gap, and
|
|
283
|
-
minting five more is the drift the scale exists to prevent. */
|
|
271
|
+
the ramp could carry: this leaf renders at `--_qm-text-paper`, the top of the ramp,
|
|
272
|
+
and minting six more is the drift the scale exists to prevent. */
|
|
284
273
|
.ProseMirror :is(h1, h2, h3, h4, h5, h6) {
|
|
285
274
|
font-size: inherit;
|
|
286
275
|
font-weight: var(--_qm-weight-strong);
|
|
@@ -413,7 +402,7 @@ li.ProseMirror-selectednode::after {
|
|
|
413
402
|
.qm-table {
|
|
414
403
|
border-collapse: separate;
|
|
415
404
|
border-spacing: 0;
|
|
416
|
-
border: var(--_qm-border-width) solid var(--_qm-
|
|
405
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
417
406
|
border-radius: var(--_qm-radius);
|
|
418
407
|
}
|
|
419
408
|
/* One rule at one strength, the frame's and every line between cells, so the grid
|
|
@@ -421,19 +410,32 @@ li.ProseMirror-selectednode::after {
|
|
|
421
410
|
`collapse`: a collapsed border cannot be rounded. */
|
|
422
411
|
.qm-table-cell {
|
|
423
412
|
position: relative;
|
|
424
|
-
border-right: var(--_qm-border-width) solid var(--_qm-
|
|
425
|
-
border-bottom: var(--_qm-border-width) solid var(--_qm-
|
|
413
|
+
border-right: var(--_qm-border-width) solid var(--_qm-border);
|
|
414
|
+
border-bottom: var(--_qm-border-width) solid var(--_qm-border);
|
|
426
415
|
padding: var(--_qm-space) var(--_qm-space-2);
|
|
427
416
|
vertical-align: top;
|
|
428
417
|
font-weight: inherit;
|
|
429
418
|
text-align: inherit;
|
|
430
419
|
}
|
|
431
|
-
.qm-table
|
|
420
|
+
.qm-table-cell[data-edge-end] {
|
|
432
421
|
border-right: none;
|
|
433
422
|
}
|
|
434
|
-
.qm-table
|
|
423
|
+
.qm-table-cell[data-edge-bottom] {
|
|
435
424
|
border-bottom: none;
|
|
436
425
|
}
|
|
426
|
+
/* The rails: a row of no height above the first and a column of no width before the
|
|
427
|
+
first, which the grips and border handles hang off rather than the first row's and
|
|
428
|
+
column's cells, a merged cell there standing over several lines. */
|
|
429
|
+
.qm-table-rail-cell {
|
|
430
|
+
position: relative;
|
|
431
|
+
width: 0;
|
|
432
|
+
height: 0;
|
|
433
|
+
padding: 0;
|
|
434
|
+
border: none;
|
|
435
|
+
}
|
|
436
|
+
.qm-table-rail-col {
|
|
437
|
+
width: 0;
|
|
438
|
+
}
|
|
437
439
|
/* The header row is the one weight distinction the island draws: `header` is a
|
|
438
440
|
separate field in the model rather than a flag on a row, so the surface says so.
|
|
439
441
|
It takes a tone with it, the band rung, a step toward the ink at both poles. The
|
|
@@ -444,20 +446,17 @@ li.ProseMirror-selectednode::after {
|
|
|
444
446
|
}
|
|
445
447
|
/* The corner cells take the frame's corner, inside its stroke, so a header's band or a
|
|
446
448
|
selection's wash keeps to the rounded edge rather than painting a square one over
|
|
447
|
-
it.
|
|
448
|
-
.qm-table
|
|
449
|
+
it. A merged cell takes each corner its span reaches. */
|
|
450
|
+
.qm-table-cell[data-corners~='start-start'] {
|
|
449
451
|
border-start-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
450
452
|
}
|
|
451
|
-
.qm-table
|
|
453
|
+
.qm-table-cell[data-corners~='start-end'] {
|
|
452
454
|
border-start-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
453
455
|
}
|
|
454
|
-
.qm-table
|
|
455
|
-
> :is(:last-child, thead:has(+ tbody:empty))
|
|
456
|
-
> tr:last-child
|
|
457
|
-
> .qm-table-cell:first-child {
|
|
456
|
+
.qm-table-cell[data-corners~='end-start'] {
|
|
458
457
|
border-end-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
459
458
|
}
|
|
460
|
-
.qm-table
|
|
459
|
+
.qm-table-cell[data-corners~='end-end'] {
|
|
461
460
|
border-end-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
462
461
|
}
|
|
463
462
|
/* A cell holds a whole nested view. Its measure floor keeps an empty table from
|
|
@@ -659,10 +658,14 @@ li.ProseMirror-selectednode::after {
|
|
|
659
658
|
/* The held column's alignment cluster, hung off its header cell and floating over the
|
|
660
659
|
first row, the band's top edge being the grips'. Up only while its column is held and
|
|
661
660
|
absent otherwise (`table-view.ts` §`alignCluster`), so the reveal is `hidden` rather
|
|
662
|
-
than the band's opacity. It
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
661
|
+
than the band's opacity. It is a row of glyphs floating over text, the format popover's
|
|
662
|
+
family (ARCHITECTURE §Styling), and takes that recipe: the popover's translucent
|
|
663
|
+
surface over a blur, the edge and `--_qm-lift` for its separation, and a scale-in on
|
|
664
|
+
arrival, stated from its starting style since the codec shows it by clearing
|
|
665
|
+
`hidden`. Its geometry is the popover's too, a row of toggles held one rung in from
|
|
666
|
+
a surface at the card's radius, so the two floating rows of glyphs are one height,
|
|
667
|
+
one shape and one material. The pressed toggle takes the selected grip's tint and
|
|
668
|
+
accent: it names the held column's alignment the way the grip names the held line. */
|
|
666
669
|
.qm-table-align {
|
|
667
670
|
position: absolute;
|
|
668
671
|
z-index: 2;
|
|
@@ -671,10 +674,21 @@ li.ProseMirror-selectednode::after {
|
|
|
671
674
|
width: max-content;
|
|
672
675
|
margin-inline: auto;
|
|
673
676
|
display: flex;
|
|
677
|
+
gap: var(--_qm-space-half);
|
|
678
|
+
padding: var(--_qm-space);
|
|
674
679
|
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
675
|
-
border-radius: var(--_qm-radius
|
|
676
|
-
background: var(--_qm-surface-
|
|
680
|
+
border-radius: var(--_qm-radius);
|
|
681
|
+
background: var(--_qm-surface-popover);
|
|
682
|
+
backdrop-filter: blur(var(--_qm-blur));
|
|
683
|
+
-webkit-backdrop-filter: blur(var(--_qm-blur));
|
|
677
684
|
box-shadow: var(--_qm-lift);
|
|
685
|
+
transition:
|
|
686
|
+
opacity var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
687
|
+
transform var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
688
|
+
@starting-style {
|
|
689
|
+
opacity: 0;
|
|
690
|
+
transform: scale(0.94);
|
|
691
|
+
}
|
|
678
692
|
}
|
|
679
693
|
.qm-table-align[hidden] {
|
|
680
694
|
display: none;
|
|
@@ -684,6 +698,30 @@ li.ProseMirror-selectednode::after {
|
|
|
684
698
|
.qm-table-align[data-header] {
|
|
685
699
|
margin-inline: auto 0;
|
|
686
700
|
}
|
|
701
|
+
/* The merge and split cluster hangs under the held block's first cell, flush with its
|
|
702
|
+
leading edge, its options worded. */
|
|
703
|
+
.qm-table-align[data-cells] {
|
|
704
|
+
margin-inline: 0 auto;
|
|
705
|
+
}
|
|
706
|
+
.qm-table-cells-option {
|
|
707
|
+
display: flex;
|
|
708
|
+
align-items: center;
|
|
709
|
+
min-height: var(--_qm-tap-min);
|
|
710
|
+
padding: 0 var(--_qm-space-2);
|
|
711
|
+
border: none;
|
|
712
|
+
border-radius: var(--_qm-radius-inner);
|
|
713
|
+
background: none;
|
|
714
|
+
color: var(--_qm-ink);
|
|
715
|
+
font: inherit;
|
|
716
|
+
white-space: nowrap;
|
|
717
|
+
cursor: pointer;
|
|
718
|
+
}
|
|
719
|
+
.qm-table-cells-option:hover {
|
|
720
|
+
background: var(--_qm-surface-hover);
|
|
721
|
+
}
|
|
722
|
+
.qm-table-cells-option[hidden] {
|
|
723
|
+
display: none;
|
|
724
|
+
}
|
|
687
725
|
.qm-table-align-option {
|
|
688
726
|
display: flex;
|
|
689
727
|
align-items: center;
|
|
@@ -2,34 +2,28 @@ import { Schema } from 'prosemirror-model';
|
|
|
2
2
|
import type { NodeType } from 'prosemirror-model';
|
|
3
3
|
/** The full field schema: every block kind, container, mark, and island. */
|
|
4
4
|
export declare const blockSchema: Schema<string, string>;
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
5
|
+
/**
|
|
6
|
+
* The constrained inline schema: one paragraph, no containers or islands, the full mark set (a
|
|
7
|
+
* `richtext(inline)` field).
|
|
8
|
+
*/
|
|
7
9
|
export declare const inlineSchema: Schema<string, string>;
|
|
8
10
|
/**
|
|
9
|
-
* The inline schema with no mark types
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* restates: `toggleMark` has no type to apply, the mark input rules build nothing,
|
|
14
|
-
* a paste parses its marks away, and `decode` drops any a stray content arrives
|
|
15
|
-
* carrying (CODEC §Inline mode).
|
|
11
|
+
* The inline schema with no mark types: a `plaintext(inline)` field, whose value is literal text
|
|
12
|
+
* the boundary refuses to coerce back once it carries a mark. Declaring none makes that structural:
|
|
13
|
+
* `toggleMark` has no type, the mark input rules build nothing, a paste parses marks away, and
|
|
14
|
+
* `decode` drops any a stray content carries (CODEC §Inline mode).
|
|
16
15
|
*/
|
|
17
16
|
export declare const plaintextSchema: Schema<string, never>;
|
|
18
17
|
/**
|
|
19
|
-
* A table cell: the inline schema plus `hard_break`, which a `TableCell` stores as a
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* path, which folds a cell's `continues` lines into that one paragraph.
|
|
18
|
+
* A table cell: the inline schema plus `hard_break`, which a `TableCell` stores as a `\n` in its
|
|
19
|
+
* `text`. Still one paragraph; a break is Shift-Enter's (`breaks.ts`). Declaring it routes `decode`
|
|
20
|
+
* through the plain path, folding a cell's `continues` lines into that paragraph.
|
|
23
21
|
*/
|
|
24
22
|
export declare const cellSchema: Schema<"hard_break", string>;
|
|
25
23
|
/**
|
|
26
|
-
* A `text` field declaring `multiline: true`: paragraphs and hard breaks, no marks,
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* A paragraph and a break both store one `\n`, so a paragraph here is a line and
|
|
31
|
-
* says so (`data-qm-line`): `prose.css` draws a run of them at the leading, and a
|
|
32
|
-
* boundary reads as the one line down that a break does.
|
|
24
|
+
* A `text` field declaring `multiline: true`: paragraphs and hard breaks, no marks, islands or
|
|
25
|
+
* containers, the shape `Content::is_plain` admits. A paragraph and a break both store one `\n`, so
|
|
26
|
+
* a paragraph here is a line (`data-qm-line`): `prose.css` draws a run of them at the leading.
|
|
33
27
|
*/
|
|
34
28
|
export declare const plainSchema: Schema<"text" | "doc" | "paragraph" | "hard_break", never>;
|
|
35
29
|
/** The schema a leaf of the declared type mounts: `inline` narrows either type to one
|
|
@@ -46,7 +40,8 @@ export declare function isInlineSchema(schema: Schema): boolean;
|
|
|
46
40
|
/** True for {@link blockSchema}, the one holding blocks past the paragraph: what a
|
|
47
41
|
* leaf's island, gap-cursor and slash machinery mounts on. */
|
|
48
42
|
export declare function isBlockSchema(schema: Schema): boolean;
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
43
|
+
/**
|
|
44
|
+
* Whether `type` can hold a within-block line break, which a heading cannot (§`heading`); a `\n`
|
|
45
|
+
* landing in a textblock and decode's continued segments turn on it (`breaks.ts`).
|
|
46
|
+
*/
|
|
52
47
|
export declare function takesLineBreak(type: NodeType): boolean;
|
|
@@ -1,43 +1,41 @@
|
|
|
1
|
-
// The PM schema: the codec owns it; decode/encode target it. Nodes mirror the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// one-line `text`; `plainSchema` is a `text` field declaring `multiline: true`,
|
|
9
|
-
// paragraphs and hard breaks and nothing else; `cellSchema` is the inline form with
|
|
10
|
-
// hard breaks, for a table cell. Same decode/lower/position machinery,
|
|
11
|
-
// narrower shapes. Anchors are not marks here (decorations).
|
|
1
|
+
// The PM schema: the codec owns it; decode/encode target it. Nodes mirror the content block kinds
|
|
2
|
+
// (para/heading/code/rule/island) and container nesting (list_item/quote); marks mirror the content
|
|
3
|
+
// formatting set. Each set is closed upstream, so the schema names its whole vocabulary.
|
|
4
|
+
// `blockSchema` is the full field; `inlineSchema` the single-textblock form for a one-line
|
|
5
|
+
// `richtext`; `plaintextSchema` that without marks, for a one-line `text`; `plainSchema` a `text`
|
|
6
|
+
// field declaring `multiline: true` (paragraphs and hard breaks only); `cellSchema` the inline form
|
|
7
|
+
// with hard breaks, for a table cell. Anchors are not marks here (decorations).
|
|
12
8
|
//
|
|
13
|
-
// `toDOM` and `parseDOM` are one tier
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// marks, a `<table>` being the block leaf's clipboard parser's (`table-paste.ts`).
|
|
9
|
+
// `toDOM` and `parseDOM` are one tier: a copy and a paste inside one body run the whole document
|
|
10
|
+
// through them (CODEC §"Markdown at the edges"), so every attribute written here is read back here.
|
|
11
|
+
// Foreign HTML spells none of this package's `data-qm-*` names; the rules that widen that door on
|
|
12
|
+
// purpose are the fence's and a span's styled marks, a `<table>` being the block leaf's clipboard
|
|
13
|
+
// parser's (`table-paste.ts`).
|
|
19
14
|
import { Schema } from 'prosemirror-model';
|
|
20
15
|
import { islandBlockSpec, islandInlineSpec } from './islands.js';
|
|
21
16
|
import { taskBody, taskChecked, taskDOM } from './tasks.js';
|
|
22
17
|
import { rendersHref, storableUrl } from './urls.js';
|
|
23
18
|
// ── Marks spelled as style ──────────────────────────────────────────────────
|
|
24
|
-
// A word processor writes formatting as a span's inline style rather than
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// a
|
|
28
|
-
//
|
|
29
|
-
// copy off any page.
|
|
19
|
+
// A word processor writes formatting as a span's inline style rather than a tag: Google Docs spells
|
|
20
|
+
// all four marks below that way, and wraps a whole copy in a `<b>` stating a regular weight. Read
|
|
21
|
+
// off a `<span>` alone (the same style on a cell or heading is that block's face). An underline
|
|
22
|
+
// inside a link is the link's own: Docs underlines every one, and Chrome inlines the computed
|
|
23
|
+
// decoration on a copy.
|
|
30
24
|
/** Whether `el`'s inline style states a bold weight. */
|
|
31
25
|
const bold = (el) => /^(?:bold(?:er)?|[6-9]\d\d)$/.test(el.style.fontWeight);
|
|
32
26
|
/** The text decoration `el` states, as a shorthand or a longhand. */
|
|
33
27
|
const decoration = (el) => `${el.style.textDecoration} ${el.style.textDecorationLine}`;
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
28
|
+
/**
|
|
29
|
+
* A mark a `<span>` carries where `styled` holds of it. Not consuming, so every mark a span states
|
|
30
|
+
* matches, and so do the span's own rules.
|
|
31
|
+
*/
|
|
36
32
|
const styledSpan = (styled) => ({
|
|
37
33
|
tag: 'span',
|
|
38
34
|
consuming: false,
|
|
39
35
|
getAttrs: (el) => styled(el) && null
|
|
40
36
|
});
|
|
37
|
+
/** `s` as a CSS string literal. */
|
|
38
|
+
const cssString = (s) => `"${s.replace(/[\\"]/g, '\\$&').replace(/\n/g, '\\a ')}"`;
|
|
41
39
|
// ── Marks (the block and inline schemas share them; plaintext declares none) ─
|
|
42
40
|
const marks = {
|
|
43
41
|
// Order matters: it fixes mark-set sort order and parse precedence. `link`
|
|
@@ -74,6 +72,29 @@ const marks = {
|
|
|
74
72
|
toDOM: () => ['s', 0]
|
|
75
73
|
},
|
|
76
74
|
code: { parseDOM: [{ tag: 'code' }], toDOM: () => ['code', 0] },
|
|
75
|
+
// One family a character, PM's default exclusion of a mark by its own type: a second family
|
|
76
|
+
// replaces the first, as the store's keyed `add` does. A family the page cannot draw falls to
|
|
77
|
+
// the root's face. A span styling `font-family` is the source's face, never a mark.
|
|
78
|
+
font_family: {
|
|
79
|
+
attrs: { name: { default: '' } },
|
|
80
|
+
parseDOM: [
|
|
81
|
+
{
|
|
82
|
+
tag: 'span[data-qm-family]',
|
|
83
|
+
getAttrs: (el) => {
|
|
84
|
+
const name = el.getAttribute('data-qm-family');
|
|
85
|
+
return name ? { name } : false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
toDOM: (mark) => {
|
|
90
|
+
const name = mark.attrs.name;
|
|
91
|
+
return [
|
|
92
|
+
'span',
|
|
93
|
+
{ 'data-qm-family': name, style: `font-family: ${cssString(name)}, var(--_qm-font)` },
|
|
94
|
+
0
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
},
|
|
77
98
|
link: {
|
|
78
99
|
attrs: { href: { default: '' } },
|
|
79
100
|
inclusive: false,
|
|
@@ -84,11 +105,8 @@ const marks = {
|
|
|
84
105
|
getAttrs: (el) => ({ href: storableUrl(el.getAttribute('data-qm-href') ?? '') })
|
|
85
106
|
}
|
|
86
107
|
],
|
|
87
|
-
// A refused href draws as a bare span
|
|
88
|
-
//
|
|
89
|
-
// span rather than going with the tag — an `href` no renderer will follow is one
|
|
90
|
-
// the document still holds, and a copy is not the explicit conversion allowed to
|
|
91
|
-
// lose it.
|
|
108
|
+
// A refused href draws as a bare span, unstyled and unclickable, and the mark keeps its
|
|
109
|
+
// value for encode: a copy is not the explicit conversion allowed to lose it.
|
|
92
110
|
toDOM: (mark) => {
|
|
93
111
|
const href = mark.attrs.href;
|
|
94
112
|
return rendersHref(href) ? ['a', { href }, 0] : ['span', { 'data-qm-href': href }, 0];
|
|
@@ -96,15 +114,14 @@ const marks = {
|
|
|
96
114
|
}
|
|
97
115
|
};
|
|
98
116
|
// ── The fence's language ────────────────────────────────────────────────────
|
|
99
|
-
// Minted by the fence shorthand
|
|
100
|
-
//
|
|
101
|
-
// highlighted page arrives carrying the language that page stated, one copied inside
|
|
102
|
-
// the body keeps its own.
|
|
117
|
+
// Minted by the fence shorthand (`inputrules.ts`) and by a paste: a fence copied off a highlighted
|
|
118
|
+
// page carries the page's language, one copied inside the body keeps its own.
|
|
103
119
|
/** A `language-x` / `lang-x` token in a class list. */
|
|
104
120
|
const LANG_CLASS = /(?:^|\s)lang(?:uage)?-(\S+)/;
|
|
105
|
-
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
121
|
+
/**
|
|
122
|
+
* The language a `<pre>` states: `data-lang` (what `toDOM` writes), else the class convention on
|
|
123
|
+
* the `pre` or its `code`. `null` for none or an empty value.
|
|
124
|
+
*/
|
|
108
125
|
function fenceLang(pre) {
|
|
109
126
|
const stated = pre.getAttribute('data-lang');
|
|
110
127
|
if (stated)
|
|
@@ -125,10 +142,9 @@ const blockNodes = {
|
|
|
125
142
|
parseDOM: [{ tag: 'p' }],
|
|
126
143
|
toDOM: () => ['p', 0]
|
|
127
144
|
},
|
|
128
|
-
// A heading holds no `hard_break`:
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
// conversion onto a heading drops it, rather than each door holding the rule.
|
|
145
|
+
// A heading holds no `hard_break`: the store clears a `continues` after one and markdown has no
|
|
146
|
+
// syntax for a break inside it. Declaring it out makes that structural rather than a rule each
|
|
147
|
+
// door holds.
|
|
132
148
|
heading: {
|
|
133
149
|
content: '(text | island_inline)*',
|
|
134
150
|
group: 'block',
|
|
@@ -174,12 +190,11 @@ const blockNodes = {
|
|
|
174
190
|
{
|
|
175
191
|
tag: 'ol',
|
|
176
192
|
getAttrs: (el) => {
|
|
177
|
-
// A list stating no `start` starts at one, the absence `toDOM` writes
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// one — `0.` is an ordinal `importMarkdown` produces and the store keeps.
|
|
193
|
+
// A list stating no `start` starts at one, the absence `toDOM` writes;
|
|
194
|
+
// `Number(null)` would read it as zero. A value the store would normalize away
|
|
195
|
+
// reads as that absence too, since a PM doc keeping one would disagree with the
|
|
196
|
+
// store for the session (CODEC §Reconciliation). Zero is not one: `0.` is an
|
|
197
|
+
// ordinal `importMarkdown` produces.
|
|
183
198
|
const stated = el.getAttribute('start');
|
|
184
199
|
const start = stated ? Number(stated) : 1;
|
|
185
200
|
return { start: Number.isSafeInteger(start) && start >= 0 ? start : 1 };
|
|
@@ -213,9 +228,8 @@ const blockNodes = {
|
|
|
213
228
|
// ── Inline nodes ────────────────────────────────────────────────────────────
|
|
214
229
|
const inlineLeafNodes = {
|
|
215
230
|
text: { group: 'inline' },
|
|
216
|
-
// `linebreakReplacement`
|
|
217
|
-
//
|
|
218
|
-
// rather than flattening them to spaces, and a break is a `continues` line.
|
|
231
|
+
// `linebreakReplacement` makes a join across a `code_block`'s edge lossless: `Transform.join`
|
|
232
|
+
// and `setBlockType` convert its `\n`s to breaks, which are `continues` lines.
|
|
219
233
|
hard_break: {
|
|
220
234
|
inline: true,
|
|
221
235
|
group: 'inline',
|
|
@@ -231,45 +245,38 @@ export const blockSchema = new Schema({
|
|
|
231
245
|
nodes: { ...blockNodes, ...inlineLeafNodes },
|
|
232
246
|
marks
|
|
233
247
|
});
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
// model level: there is no second block for a split to land.
|
|
248
|
+
// `doc: "paragraph"` (exactly one child) makes Enter a no-op at the model level: no second block
|
|
249
|
+
// for a split to land.
|
|
237
250
|
const inlineNodes = {
|
|
238
251
|
doc: { content: 'paragraph' },
|
|
239
252
|
paragraph: { content: 'inline*', toDOM: () => ['p', 0] },
|
|
240
253
|
text: { group: 'inline' }
|
|
241
254
|
};
|
|
242
|
-
/**
|
|
243
|
-
*
|
|
255
|
+
/**
|
|
256
|
+
* The constrained inline schema: one paragraph, no containers or islands, the full mark set (a
|
|
257
|
+
* `richtext(inline)` field).
|
|
258
|
+
*/
|
|
244
259
|
export const inlineSchema = new Schema({ nodes: inlineNodes, marks });
|
|
245
260
|
/**
|
|
246
|
-
* The inline schema with no mark types
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* restates: `toggleMark` has no type to apply, the mark input rules build nothing,
|
|
251
|
-
* a paste parses its marks away, and `decode` drops any a stray content arrives
|
|
252
|
-
* carrying (CODEC §Inline mode).
|
|
261
|
+
* The inline schema with no mark types: a `plaintext(inline)` field, whose value is literal text
|
|
262
|
+
* the boundary refuses to coerce back once it carries a mark. Declaring none makes that structural:
|
|
263
|
+
* `toggleMark` has no type, the mark input rules build nothing, a paste parses marks away, and
|
|
264
|
+
* `decode` drops any a stray content carries (CODEC §Inline mode).
|
|
253
265
|
*/
|
|
254
266
|
export const plaintextSchema = new Schema({ nodes: inlineNodes, marks: {} });
|
|
255
267
|
/**
|
|
256
|
-
* A table cell: the inline schema plus `hard_break`, which a `TableCell` stores as a
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* path, which folds a cell's `continues` lines into that one paragraph.
|
|
268
|
+
* A table cell: the inline schema plus `hard_break`, which a `TableCell` stores as a `\n` in its
|
|
269
|
+
* `text`. Still one paragraph; a break is Shift-Enter's (`breaks.ts`). Declaring it routes `decode`
|
|
270
|
+
* through the plain path, folding a cell's `continues` lines into that paragraph.
|
|
260
271
|
*/
|
|
261
272
|
export const cellSchema = new Schema({
|
|
262
273
|
nodes: { ...inlineNodes, hard_break: inlineLeafNodes.hard_break },
|
|
263
274
|
marks
|
|
264
275
|
});
|
|
265
276
|
/**
|
|
266
|
-
* A `text` field declaring `multiline: true`: paragraphs and hard breaks, no marks,
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* A paragraph and a break both store one `\n`, so a paragraph here is a line and
|
|
271
|
-
* says so (`data-qm-line`): `prose.css` draws a run of them at the leading, and a
|
|
272
|
-
* boundary reads as the one line down that a break does.
|
|
277
|
+
* A `text` field declaring `multiline: true`: paragraphs and hard breaks, no marks, islands or
|
|
278
|
+
* containers, the shape `Content::is_plain` admits. A paragraph and a break both store one `\n`, so
|
|
279
|
+
* a paragraph here is a line (`data-qm-line`): `prose.css` draws a run of them at the leading.
|
|
273
280
|
*/
|
|
274
281
|
export const plainSchema = new Schema({
|
|
275
282
|
nodes: {
|
|
@@ -301,9 +308,10 @@ export function isInlineSchema(schema) {
|
|
|
301
308
|
export function isBlockSchema(schema) {
|
|
302
309
|
return !!schema.nodes.blockquote;
|
|
303
310
|
}
|
|
304
|
-
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
311
|
+
/**
|
|
312
|
+
* Whether `type` can hold a within-block line break, which a heading cannot (§`heading`); a `\n`
|
|
313
|
+
* landing in a textblock and decode's continued segments turn on it (`breaks.ts`).
|
|
314
|
+
*/
|
|
307
315
|
export function takesLineBreak(type) {
|
|
308
316
|
const br = type.schema.nodes.hard_break;
|
|
309
317
|
return !!br && type.contentMatch.matchType(br) !== null;
|