mnfst 0.5.219 → 0.5.221
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/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.code.js +7 -1
- package/lib/manifest.css +65 -39
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.edit.css +16 -27
- package/lib/manifest.edit.js +51 -13
- package/lib/manifest.integrity.json +7 -7
- package/lib/manifest.markdown.js +11 -5
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.text.edit.css +49 -12
- package/lib/manifest.text.edit.js +53 -6
- package/package.json +1 -1
package/lib/manifest.code.js
CHANGED
|
@@ -635,6 +635,12 @@ async function setupCodeGroup(group) {
|
|
|
635
635
|
pre.appendChild(copyBtn);
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
+
// Stamped only once the group is FULLY built (header, panels, copy). The
|
|
639
|
+
// re-bind pass keys off this, never off data-group-processed, which is
|
|
640
|
+
// claimed synchronously at entry — a scan landing between that claim and
|
|
641
|
+
// the awaited builds above would otherwise wire (and _groupBound-poison)
|
|
642
|
+
// a half-built group.
|
|
643
|
+
pre.dataset.groupBuilt = 'yes';
|
|
638
644
|
wireGroup(pre);
|
|
639
645
|
}
|
|
640
646
|
|
|
@@ -817,7 +823,7 @@ function processOne(el) {
|
|
|
817
823
|
// editors arrive dead. Re-attach behavior to that adopted markup. Cheap (no
|
|
818
824
|
// highlighting), so it runs eagerly rather than through the IntersectionObserver.
|
|
819
825
|
function rebindAdopted(root = document) {
|
|
820
|
-
root.querySelectorAll('[x-code-group][data-group-
|
|
826
|
+
root.querySelectorAll('[x-code-group][data-group-built="yes"]').forEach(g => {
|
|
821
827
|
wireGroup(g);
|
|
822
828
|
Array.from(g.children).forEach(c => { if (c.dataset && c.dataset.codeBlockBuilt === 'yes') rebindBlock(c); });
|
|
823
829
|
});
|
package/lib/manifest.css
CHANGED
|
@@ -2946,18 +2946,18 @@
|
|
|
2946
2946
|
itself; the affordance only has to be there when you go looking for it.
|
|
2947
2947
|
While an inner affordance is engaged (text caret, handle, row), the region
|
|
2948
2948
|
outline stands down so only the element being edited is highlighted. */
|
|
2949
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *) {
|
|
2949
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *) {
|
|
2950
2950
|
outline: 1px dashed transparent;
|
|
2951
2951
|
outline-offset: 6px;
|
|
2952
2952
|
border-radius: var(--radius);
|
|
2953
2953
|
transition: outline-color 0.12s ease;
|
|
2954
2954
|
}
|
|
2955
2955
|
|
|
2956
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus))) {
|
|
2956
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus))) {
|
|
2957
2957
|
outline-color: var(--color-line);
|
|
2958
2958
|
}
|
|
2959
2959
|
|
|
2960
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
2960
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
2961
2961
|
content: attr(data-edit-label);
|
|
2962
2962
|
position: absolute;
|
|
2963
2963
|
inset-block-start: calc(-0.6em - 6px);
|
|
@@ -2975,7 +2975,7 @@
|
|
|
2975
2975
|
transition: opacity 0.12s ease;
|
|
2976
2976
|
}
|
|
2977
2977
|
|
|
2978
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus)))::before {
|
|
2978
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus)))::before {
|
|
2979
2979
|
opacity: 1;
|
|
2980
2980
|
}
|
|
2981
2981
|
|
|
@@ -2986,29 +2986,9 @@
|
|
|
2986
2986
|
transition: opacity 0.12s ease, outline-color 0.12s ease;
|
|
2987
2987
|
}
|
|
2988
2988
|
|
|
2989
|
-
/*
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
item would take a cell). No full-row outline — the grip and the cursor
|
|
2993
|
-
say "draggable" without boxing the content. */
|
|
2994
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable])::before {
|
|
2995
|
-
content: '';
|
|
2996
|
-
align-self: center;
|
|
2997
|
-
flex: 0 0 auto;
|
|
2998
|
-
width: var(--edit-grip, 0.6rem);
|
|
2999
|
-
height: 1rem;
|
|
3000
|
-
margin-inline-end: calc(var(--edit-grip, 0.6rem) / 2);
|
|
3001
|
-
background-image: radial-gradient(circle, var(--color-content-subtle) 1px, transparent 1px);
|
|
3002
|
-
background-size: 0.3rem 0.3rem;
|
|
3003
|
-
background-position: center;
|
|
3004
|
-
opacity: 0;
|
|
3005
|
-
transition: opacity 0.12s ease;
|
|
3006
|
-
pointer-events: none;
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable]):is(:hover, :focus-visible)::before {
|
|
3010
|
-
opacity: 0.9;
|
|
3011
|
-
}
|
|
2989
|
+
/* No injected grip: the row itself is the handle (grab cursor), while text
|
|
2990
|
+
inside stays a text surface (text cursor, below). Rows that are pure text
|
|
2991
|
+
still reorder by keyboard. */
|
|
3012
2992
|
|
|
3013
2993
|
:where([data-edit-sortable], [data-edit-movable]):focus-visible {
|
|
3014
2994
|
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
@@ -3031,6 +3011,14 @@
|
|
|
3031
3011
|
pointer-events: none;
|
|
3032
3012
|
}
|
|
3033
3013
|
|
|
3014
|
+
/* Valid cross-region destinations during a drag: a dashed invitation, and
|
|
3015
|
+
enough height that an EMPTY region can be dropped onto at all. */
|
|
3016
|
+
:where([data-edit-drop-target]) {
|
|
3017
|
+
outline: 1px dashed color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 55%, transparent);
|
|
3018
|
+
outline-offset: 4px;
|
|
3019
|
+
min-height: 2.5rem;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3034
3022
|
/* Nothing else should react while a drag is in progress */
|
|
3035
3023
|
:where([data-edit-dragging-in]) {
|
|
3036
3024
|
cursor: grabbing;
|
|
@@ -3062,6 +3050,7 @@
|
|
|
3062
3050
|
1px line, so focusing doesn't make the page jump a weight class. */
|
|
3063
3051
|
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"],
|
|
3064
3052
|
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"] {
|
|
3053
|
+
cursor: text;
|
|
3065
3054
|
outline: 1px solid transparent;
|
|
3066
3055
|
outline-offset: 2px;
|
|
3067
3056
|
border-radius: 3px;
|
|
@@ -4261,13 +4250,9 @@
|
|
|
4261
4250
|
@layer components {
|
|
4262
4251
|
|
|
4263
4252
|
:where([data-text-edit]) {
|
|
4264
|
-
|
|
4265
|
-
max-height: var(--text-edit-max-height, none);
|
|
4253
|
+
position: relative;
|
|
4266
4254
|
overflow-y: auto;
|
|
4267
|
-
|
|
4268
|
-
background: var(--text-edit-background, var(--color-surface-1));
|
|
4269
|
-
border: 1px solid var(--color-line);
|
|
4270
|
-
border-radius: var(--radius);
|
|
4255
|
+
max-height: var(--text-edit-max-height, none);
|
|
4271
4256
|
outline: none;
|
|
4272
4257
|
|
|
4273
4258
|
/* Page-level styling — set by .page controls, read back by $text.page.
|
|
@@ -4278,19 +4263,45 @@
|
|
|
4278
4263
|
text-align: var(--text-edit-align, start);
|
|
4279
4264
|
color: var(--text-edit-color, inherit);
|
|
4280
4265
|
|
|
4281
|
-
&:focus {
|
|
4282
|
-
border-color: var(--color-content-subtle);
|
|
4283
|
-
}
|
|
4284
|
-
|
|
4285
4266
|
/* Flagged from script: :empty never matches the <br> a contenteditable
|
|
4286
|
-
keeps, and the tag holding it is not predictable after a wipe.
|
|
4267
|
+
keeps, and the tag holding it is not predictable after a wipe. The
|
|
4268
|
+
placeholder OVERLAYS the first line (it is not a line of its own), so
|
|
4269
|
+
the caret sits on top of it and nothing jumps when typing starts. */
|
|
4287
4270
|
&[data-text-edit-empty]::before {
|
|
4288
4271
|
content: attr(placeholder);
|
|
4272
|
+
position: absolute;
|
|
4273
|
+
inset-block-start: var(--text-edit-padding, 0.75rem);
|
|
4274
|
+
inset-inline: var(--text-edit-padding, 0.75rem);
|
|
4289
4275
|
color: var(--color-content-subtle);
|
|
4290
4276
|
pointer-events: none;
|
|
4291
4277
|
}
|
|
4292
4278
|
}
|
|
4293
4279
|
|
|
4280
|
+
/* An unstyled area has no field padding, so its placeholder hugs the line. */
|
|
4281
|
+
:where([data-text-edit].unstyle)[data-text-edit-empty]::before {
|
|
4282
|
+
inset-block-start: 0;
|
|
4283
|
+
inset-inline: 0;
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
/* The FIELD look. `.unstyle` drops it so an area can sit seamlessly in a
|
|
4287
|
+
page — a WYSIWYG document inherits its element's own type and colours;
|
|
4288
|
+
everything above (caret custody, placeholder, commands) still works. */
|
|
4289
|
+
:where([data-text-edit]:not(.unstyle)) {
|
|
4290
|
+
min-height: var(--text-edit-min-height, 8rem);
|
|
4291
|
+
padding: var(--text-edit-padding, 0.75rem);
|
|
4292
|
+
background: var(--text-edit-background, var(--color-surface-1));
|
|
4293
|
+
border: 1px solid var(--color-line);
|
|
4294
|
+
border-radius: var(--radius);
|
|
4295
|
+
|
|
4296
|
+
/* Held while the area owns the editing session — a <select> or colour
|
|
4297
|
+
control takes NATIVE focus by design, and the ring must not flicker
|
|
4298
|
+
off while the writer is mid-command. */
|
|
4299
|
+
&:focus,
|
|
4300
|
+
&[data-text-edit-focused] {
|
|
4301
|
+
border-color: var(--color-content-subtle);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4294
4305
|
/* ---- Selection menu (x-text-edit-menu) ----
|
|
4295
4306
|
Positioned by the --text-edit-selection-* variables the plugin writes to
|
|
4296
4307
|
:root, so the default needs no script; restyle or reposition freely. */
|
|
@@ -4301,7 +4312,6 @@
|
|
|
4301
4312
|
left: var(--text-edit-selection-center);
|
|
4302
4313
|
top: var(--text-edit-selection-y);
|
|
4303
4314
|
transform: translate(-50%, calc(-100% - 0.5rem));
|
|
4304
|
-
display: flex;
|
|
4305
4315
|
gap: 0.25rem;
|
|
4306
4316
|
align-items: center;
|
|
4307
4317
|
padding: 0.25rem;
|
|
@@ -4311,6 +4321,22 @@
|
|
|
4311
4321
|
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
|
|
4312
4322
|
}
|
|
4313
4323
|
|
|
4324
|
+
/* display only while OPEN: an author display on the base rule would beat
|
|
4325
|
+
the UA's closed-popover display:none, leaving the menu floating fixed
|
|
4326
|
+
over the page before any selection exists. */
|
|
4327
|
+
:where([data-text-edit-menu]:popover-open) {
|
|
4328
|
+
display: flex;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
/* Without the Popover API the attribute is inert and the menu would render
|
|
4332
|
+
permanently fixed on the page — hide it there; the bubble is progressive
|
|
4333
|
+
enhancement. (In supporting browsers a closed popover is hidden natively.) */
|
|
4334
|
+
@supports not selector(:popover-open) {
|
|
4335
|
+
:where([data-text-edit-menu]) {
|
|
4336
|
+
display: none;
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4314
4340
|
/* ---- Controls ----
|
|
4315
4341
|
A control is marked wherever it lives, so a toolbar is optional markup and
|
|
4316
4342
|
never a requirement. The reset already dims [aria-disabled=true]. */
|
package/lib/manifest.data.js
CHANGED
package/lib/manifest.edit.css
CHANGED
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
itself; the affordance only has to be there when you go looking for it.
|
|
27
27
|
While an inner affordance is engaged (text caret, handle, row), the region
|
|
28
28
|
outline stands down so only the element being edited is highlighted. */
|
|
29
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *) {
|
|
29
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *) {
|
|
30
30
|
outline: 1px dashed transparent;
|
|
31
31
|
outline-offset: 6px;
|
|
32
32
|
border-radius: var(--radius);
|
|
33
33
|
transition: outline-color 0.12s ease;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus))) {
|
|
36
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus))) {
|
|
37
37
|
outline-color: var(--color-line);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
40
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
41
41
|
content: attr(data-edit-label);
|
|
42
42
|
position: absolute;
|
|
43
43
|
inset-block-start: calc(-0.6em - 6px);
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
transition: opacity 0.12s ease;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus)))::before {
|
|
58
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus)))::before {
|
|
59
59
|
opacity: 1;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -66,29 +66,9 @@
|
|
|
66
66
|
transition: opacity 0.12s ease, outline-color 0.12s ease;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
item would take a cell). No full-row outline — the grip and the cursor
|
|
73
|
-
say "draggable" without boxing the content. */
|
|
74
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable])::before {
|
|
75
|
-
content: '';
|
|
76
|
-
align-self: center;
|
|
77
|
-
flex: 0 0 auto;
|
|
78
|
-
width: var(--edit-grip, 0.6rem);
|
|
79
|
-
height: 1rem;
|
|
80
|
-
margin-inline-end: calc(var(--edit-grip, 0.6rem) / 2);
|
|
81
|
-
background-image: radial-gradient(circle, var(--color-content-subtle) 1px, transparent 1px);
|
|
82
|
-
background-size: 0.3rem 0.3rem;
|
|
83
|
-
background-position: center;
|
|
84
|
-
opacity: 0;
|
|
85
|
-
transition: opacity 0.12s ease;
|
|
86
|
-
pointer-events: none;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable]):is(:hover, :focus-visible)::before {
|
|
90
|
-
opacity: 0.9;
|
|
91
|
-
}
|
|
69
|
+
/* No injected grip: the row itself is the handle (grab cursor), while text
|
|
70
|
+
inside stays a text surface (text cursor, below). Rows that are pure text
|
|
71
|
+
still reorder by keyboard. */
|
|
92
72
|
|
|
93
73
|
:where([data-edit-sortable], [data-edit-movable]):focus-visible {
|
|
94
74
|
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
@@ -111,6 +91,14 @@
|
|
|
111
91
|
pointer-events: none;
|
|
112
92
|
}
|
|
113
93
|
|
|
94
|
+
/* Valid cross-region destinations during a drag: a dashed invitation, and
|
|
95
|
+
enough height that an EMPTY region can be dropped onto at all. */
|
|
96
|
+
:where([data-edit-drop-target]) {
|
|
97
|
+
outline: 1px dashed color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 55%, transparent);
|
|
98
|
+
outline-offset: 4px;
|
|
99
|
+
min-height: 2.5rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
114
102
|
/* Nothing else should react while a drag is in progress */
|
|
115
103
|
:where([data-edit-dragging-in]) {
|
|
116
104
|
cursor: grabbing;
|
|
@@ -142,6 +130,7 @@
|
|
|
142
130
|
1px line, so focusing doesn't make the page jump a weight class. */
|
|
143
131
|
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"],
|
|
144
132
|
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"] {
|
|
133
|
+
cursor: text;
|
|
145
134
|
outline: 1px solid transparent;
|
|
146
135
|
outline-offset: 2px;
|
|
147
136
|
border-radius: 3px;
|
package/lib/manifest.edit.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
(function () {
|
|
19
19
|
const LS_KEY = 'mnfst-edit-log';
|
|
20
|
-
const SCHEMA =
|
|
20
|
+
const SCHEMA = 7; // overlay schema version — bump when the delta shape changes
|
|
21
21
|
const HISTORY_CAP = 400; // cap the append-only log so long sessions don't grow unbounded
|
|
22
22
|
const ALL_CAPS = ['sort', 'text', 'style', 'size', 'data']; // 'data' = edit $x field values (opt-in)
|
|
23
23
|
let dragged = null, autoN = 0;
|
|
@@ -367,7 +367,12 @@
|
|
|
367
367
|
const region = key(area), path = el.getAttribute('data-edit-key');
|
|
368
368
|
if (!path) return; // not an addressable node
|
|
369
369
|
if (prop === 'text') value = sanitizeFor(el, value);
|
|
370
|
-
|
|
370
|
+
// _pre* comes from the focus/open listener; when an edit arrives without
|
|
371
|
+
// one (programmatic focus, scripted writes), the arm-time baseline keeps
|
|
372
|
+
// `before` truthful so undo can still reach the original.
|
|
373
|
+
const fallback = prop === 'text' ? area._baseText && area._baseText[path] : prop === 'class' ? area._baseClass && area._baseClass[path] : area._baseStyle && area._baseStyle[path];
|
|
374
|
+
const pre = prop === 'text' ? el._preEdit : prop === 'class' ? el._preClass : el._preStyle;
|
|
375
|
+
const before = pre !== undefined ? pre : fallback;
|
|
371
376
|
if (before === value) return;
|
|
372
377
|
log.splice(cursor); log.push({ kind: 'st-node', region, path, prop, value, before, sig: nodeSig(el) }); cursor = log.length; saveState(); refresh();
|
|
373
378
|
}
|
|
@@ -421,7 +426,7 @@
|
|
|
421
426
|
if (el.hasAttribute('data-edit-handle') || el.hasAttribute('data-edit-ghost')) continue;
|
|
422
427
|
if (el.parentElement && el.parentElement.closest('[data-text-edit]')) continue;
|
|
423
428
|
const cur = el.getAttribute('data-edit-key');
|
|
424
|
-
const base = cur ?
|
|
429
|
+
const base = cur ? ((/^([\s\S]*)#\d+$/.exec(cur) || [, cur])[1]) : staticKey(el); // ordinal = last #digits suffix
|
|
425
430
|
const n = seen[base] = (seen[base] || 0) + 1;
|
|
426
431
|
const k = n > 1 ? base + '#' + n : base;
|
|
427
432
|
if (cur !== k) { el.setAttribute('data-edit-key', k); if (cur) changes.push([cur, k]); }
|
|
@@ -775,6 +780,12 @@
|
|
|
775
780
|
// opens is a real element the author can style — by default a translucent
|
|
776
781
|
// copy of what is being dragged, showing exactly where it would land.
|
|
777
782
|
const lift = (ev) => {
|
|
783
|
+
// Valid cross-region destinations announce themselves for the whole
|
|
784
|
+
// drag: a dashed target outline, and a min-height (via CSS) so an
|
|
785
|
+
// EMPTY region has a surface to drop onto at all.
|
|
786
|
+
if (crossOK) areas().forEach(a => {
|
|
787
|
+
if (a !== area && isActive(a) && !locked(a) && capOf(a, 'sort') && classify(a) === 'static' && topArea(a)) a.setAttribute('data-edit-drop-target', '');
|
|
788
|
+
});
|
|
778
789
|
ghost = item.cloneNode(true);
|
|
779
790
|
ghost.setAttribute('data-edit-ghost', '');
|
|
780
791
|
ghost.setAttribute('x-ignore', ''); // a clone must not re-bind
|
|
@@ -822,6 +833,7 @@
|
|
|
822
833
|
const settle = (cancelled) => {
|
|
823
834
|
document.removeEventListener('pointermove', onMove);
|
|
824
835
|
document.removeEventListener('pointerup', onUp);
|
|
836
|
+
document.removeEventListener('pointercancel', onCancel);
|
|
825
837
|
document.removeEventListener('keydown', onKey, true);
|
|
826
838
|
if (frame) cancelAnimationFrame(frame);
|
|
827
839
|
if (!active) return;
|
|
@@ -832,6 +844,7 @@
|
|
|
832
844
|
if (preStyle == null) item.removeAttribute('style'); else item.setAttribute('style', preStyle);
|
|
833
845
|
item.removeAttribute('data-edit-dragging');
|
|
834
846
|
area.removeAttribute('data-edit-dragging-in');
|
|
847
|
+
document.querySelectorAll('[data-edit-drop-target]').forEach(a => a.removeAttribute('data-edit-drop-target'));
|
|
835
848
|
if (hovered !== container) hovered.removeAttribute('data-edit-dragging-in');
|
|
836
849
|
ghost = null; dragged = null;
|
|
837
850
|
if (cancelled) { announce('Cancelled'); return; }
|
|
@@ -845,10 +858,14 @@
|
|
|
845
858
|
else finishReorder(area);
|
|
846
859
|
};
|
|
847
860
|
const onUp = () => settle(false);
|
|
848
|
-
// Escape puts it back, the way every drag is expected to be escapable.
|
|
861
|
+
// Escape puts it back, the way every drag is expected to be escapable. A
|
|
862
|
+
// cancelled pointer (OS gesture, alert, pen out of range) also reverts —
|
|
863
|
+
// otherwise the item wedges position:fixed with drop targets still lit.
|
|
864
|
+
const onCancel = () => settle(true);
|
|
849
865
|
const onKey = (ev) => { if (ev.key !== 'Escape') return; ev.preventDefault(); ev.stopPropagation(); settle(true); };
|
|
850
866
|
document.addEventListener('pointermove', onMove);
|
|
851
867
|
document.addEventListener('pointerup', onUp);
|
|
868
|
+
document.addEventListener('pointercancel', onCancel);
|
|
852
869
|
document.addEventListener('keydown', onKey, true);
|
|
853
870
|
}
|
|
854
871
|
function reorderOver(container, x, y) {
|
|
@@ -910,7 +927,9 @@
|
|
|
910
927
|
const lu = unitOf(el.style.left) || 'px', tu = unitOf(el.style.top) || 'px';
|
|
911
928
|
const baseL = parseFloat(cs.left) || 0, baseT = parseFloat(cs.top) || 0, sx = e.clientX, sy = e.clientY;
|
|
912
929
|
const ov = showOverlay();
|
|
913
|
-
|
|
930
|
+
// Clamped to the containing block: a freeform card must not leave its canvas.
|
|
931
|
+
const maxL = () => Math.max(0, parent.clientWidth - el.offsetWidth), maxT = () => Math.max(0, parent.clientHeight - el.offsetHeight);
|
|
932
|
+
const move = (ev) => { ev.preventDefault(); el.style.left = toUnit(Math.min(maxL(), Math.max(0, baseL + (ev.clientX - sx))), lu, el, parent, 'w') + lu; el.style.top = toUnit(Math.min(maxT(), Math.max(0, baseT + (ev.clientY - sy))), tu, el, parent, 'h') + tu; };
|
|
914
933
|
const up = () => { document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up); hideOverlay(ov); commitStyle(area, el); };
|
|
915
934
|
document.addEventListener('pointermove', move); document.addEventListener('pointerup', up);
|
|
916
935
|
});
|
|
@@ -921,8 +940,9 @@
|
|
|
921
940
|
if (!el._moveTimer) el._preStyle = el.getAttribute('style') || ''; // baseline at start of a key burst
|
|
922
941
|
const step = e.shiftKey ? 16 : 4, cs = getComputedStyle(el), parent = el.offsetParent || el.parentElement;
|
|
923
942
|
const lu = unitOf(el.style.left) || 'px', tu = unitOf(el.style.top) || 'px';
|
|
924
|
-
|
|
925
|
-
if (d[
|
|
943
|
+
const maxL = Math.max(0, parent.clientWidth - el.offsetWidth), maxT = Math.max(0, parent.clientHeight - el.offsetHeight);
|
|
944
|
+
if (d[0]) el.style.left = toUnit(Math.min(maxL, Math.max(0, (parseFloat(cs.left) || 0) + d[0] * step)), lu, el, parent, 'w') + lu;
|
|
945
|
+
if (d[1]) el.style.top = toUnit(Math.min(maxT, Math.max(0, (parseFloat(cs.top) || 0) + d[1] * step)), tu, el, parent, 'h') + tu;
|
|
926
946
|
clearTimeout(el._moveTimer); el._moveTimer = setTimeout(() => { el._moveTimer = null; commitStyle(area, el); }, 350);
|
|
927
947
|
});
|
|
928
948
|
}
|
|
@@ -1042,6 +1062,7 @@
|
|
|
1042
1062
|
// own caret placement; a second click still collapses to a caret for fine edits.
|
|
1043
1063
|
function selectAllIn(el) {
|
|
1044
1064
|
requestAnimationFrame(() => {
|
|
1065
|
+
if (dragged || grabbed) return; // reordering, not editing
|
|
1045
1066
|
if (document.activeElement !== el) return;
|
|
1046
1067
|
try { const r = document.createRange(); r.selectNodeContents(el); const s = window.getSelection(); s.removeAllRanges(); s.addRange(r); } catch { }
|
|
1047
1068
|
});
|
|
@@ -1066,14 +1087,19 @@
|
|
|
1066
1087
|
// STATIC: literal leaves only (skip bound nodes; those belong to data/component).
|
|
1067
1088
|
function armText(area) {
|
|
1068
1089
|
armRichText(area);
|
|
1069
|
-
|
|
1070
|
-
|
|
1090
|
+
// A region whose root IS the text — <p x-edit.text="note">…</p> — has no
|
|
1091
|
+
// descendant leaves; the area itself is the editable leaf.
|
|
1092
|
+
const nodes = [...area.querySelectorAll('*')];
|
|
1093
|
+
if (!realChildren(area).length) nodes.unshift(area); // size handles are children too
|
|
1094
|
+
nodes.forEach(el => {
|
|
1095
|
+
if (realChildren(el).length || !el.textContent.trim() || el.closest('template') || el.hasAttribute('data-edit-handle')) return;
|
|
1071
1096
|
if (el.closest('[data-text-edit]')) return; // the rich editor owns this subtree
|
|
1072
1097
|
if (!capOf(el, 'text') || el.hasAttribute('x-text') || el.hasAttribute('x-html')) return;
|
|
1073
1098
|
el.setAttribute('contenteditable', 'true');
|
|
1074
1099
|
if (el._textBound) return; el._textBound = true;
|
|
1075
|
-
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim();
|
|
1076
|
-
el.addEventListener('
|
|
1100
|
+
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim(); selectAllIn(el); });
|
|
1101
|
+
el.addEventListener('keydown', (e) => { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); el.blur(); } }); // Escape finishes the edit, like the rich editor
|
|
1102
|
+
el.addEventListener('blur', () => commitStaticNode(regionOf(el) || area, el, 'text', el.innerHTML.trim())); // live region: the element may have moved since arming
|
|
1077
1103
|
});
|
|
1078
1104
|
}
|
|
1079
1105
|
|
|
@@ -1105,7 +1131,7 @@
|
|
|
1105
1131
|
if (!el.hasAttribute('data-text-edit')) el.setAttribute('contenteditable', 'true');
|
|
1106
1132
|
if (el._dvBound) return; el._dvBound = true;
|
|
1107
1133
|
const read = () => rich ? el.innerHTML.trim() : el.textContent;
|
|
1108
|
-
el.addEventListener('focus', () => { el._preEdit = read(); selectAllIn(el); });
|
|
1134
|
+
el.addEventListener('focus', () => { el._preEdit = read(); if (!el.hasAttribute('data-text-edit')) selectAllIn(el); }); // the rich editor owns its caret
|
|
1109
1135
|
el.addEventListener('focusin', () => { el._preEdit = read(); });
|
|
1110
1136
|
el.addEventListener('blur', () => commitDataValue(area, source, id, field, read(), el), true);
|
|
1111
1137
|
});
|
|
@@ -1321,7 +1347,9 @@
|
|
|
1321
1347
|
if (k) {
|
|
1322
1348
|
// Keep the tally in step so a new element never claims an ordinal
|
|
1323
1349
|
// an existing one already holds.
|
|
1324
|
-
|
|
1350
|
+
// The ordinal is the LAST #digits suffix — authored text may contain '#'.
|
|
1351
|
+
const m = /^([\s\S]*)#(\d+)$/.exec(k);
|
|
1352
|
+
const base = m ? m[1] : k, n = m ? m[2] : undefined;
|
|
1325
1353
|
seen[base] = Math.max(seen[base] || 0, n ? +n : 1);
|
|
1326
1354
|
} else {
|
|
1327
1355
|
const base = el.tagName + ':' + (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 24);
|
|
@@ -1332,6 +1360,7 @@
|
|
|
1332
1360
|
el.setAttribute('data-edit-path', pathOf(el, area)); // still what the source writer navigates by
|
|
1333
1361
|
if (!(k in area._baseClass)) area._baseClass[k] = el.getAttribute('class') || '';
|
|
1334
1362
|
if (!(k in area._baseStyle)) area._baseStyle[k] = el.getAttribute('style') || '';
|
|
1363
|
+
if (!(k in area._baseText) && !el.children.length) area._baseText[k] = el.innerHTML.trim();
|
|
1335
1364
|
};
|
|
1336
1365
|
markEl(area);
|
|
1337
1366
|
// Stop at a rich editor: its internals are its own, and marking them would
|
|
@@ -1360,6 +1389,15 @@
|
|
|
1360
1389
|
[area, ...area.querySelectorAll('[data-edit-area]')].forEach(c => { if (capOf(c, 'sort') && !locked(c)) makeSortable(c); });
|
|
1361
1390
|
[area, ...area.querySelectorAll('[data-edit-area]')].forEach(c => { if (ownsCap(c, 'size')) armSize(c); });
|
|
1362
1391
|
if (kind === 'component') armComponent(area); else armText(area);
|
|
1392
|
+
if (!area._dndBound) {
|
|
1393
|
+
area._dndBound = true;
|
|
1394
|
+
// Native HTML5 drag (a text selection, an image, a link) must never
|
|
1395
|
+
// start inside a region: it drops SELECTED TEXT into neighbouring
|
|
1396
|
+
// editable leaves, silently merging elements. All region dragging is
|
|
1397
|
+
// pointer-based.
|
|
1398
|
+
area.addEventListener('dragstart', (e) => { if (isActive(area)) e.preventDefault(); });
|
|
1399
|
+
area.addEventListener('drop', (e) => { if (isActive(area)) e.preventDefault(); }); // nothing drops INTO a region either (an OS image drop would merge into a leaf)
|
|
1400
|
+
}
|
|
1363
1401
|
if (!area._ctxBound) {
|
|
1364
1402
|
area._ctxBound = true;
|
|
1365
1403
|
// One binding for the whole area: report the block, and fall back to the
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-X30RFvbwT9qEIaMsMUumnW54ZzcnY5aY9QQeXPG8/0sI6VAbAw3+FZ6iuZb6b4cu",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-oPSItIjZCJghmbtcE+6wx6pU3RL925Z4itl/YWGxSpreVY8E5gjF9jI6NXNDCVm6",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
|
|
7
7
|
"manifest.chat.js": "sha384-88DfMfJLoi2oxclVD2iIMDkIGAExMXq5k47mekUredlicLaP3oZY5bwtwqs+TqDF",
|
|
8
|
-
"manifest.code.js": "sha384-
|
|
8
|
+
"manifest.code.js": "sha384-uhE6Ha+pCgzSgBv5L6Ev7g1toOSSwBOiMAmvWVzkrikknFxkHEh5YKfrfFIxN3vw",
|
|
9
9
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
10
10
|
"manifest.colorpicker.js": "sha384-BhcCe8cGcd0DTJNwzXWWY+FQNRfSFFl6XqfHIFP8yHg/aXFks1NIC6lVx0Sr1VkM",
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-4bc3HgPvF9DHMiOG08w+nWXI7Hho01RuAPAUL9xyLxOiS2igeZpulCVcvr5aT9v6",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-4ulI/WUv1NxDgdf7WzzffU2N0HcB0lQpyOr7PNFrhdkvVmOssh6j8RjwO3emZt7U",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
16
|
"manifest.defer.js": "sha384-WcCf8YQ1bKIXCLSZ24a0bJwepbYntYc8TzQM50hklo5VMIH8AtzDnwl6WU4prRv1",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
18
18
|
"manifest.dropdowns.js": "sha384-0ilj9AvRph0LacIBJSqwpu1fvPRn5gcWbhIG1SOpaoM/Q8idMbKBLupwM+5O0UaX",
|
|
19
|
-
"manifest.edit.js": "sha384-
|
|
19
|
+
"manifest.edit.js": "sha384-63ym+1EWz+JKvwwH3v2XlR4q5alha/ShMiIJsbDbDS7eVymu5zgghsh8JRVeax+V",
|
|
20
20
|
"manifest.export.js": "sha384-Rx6XvqBZc4apzP/FwcDr6yIln4/zNa08Q4ZqBZcEqUz1aNZQfl4h0+Ae7mqCQyuG",
|
|
21
21
|
"manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
|
|
22
22
|
"manifest.localization.js": "sha384-EImsIw8Z3l13V5eH9ZJF/yfsOfIvR1oPRNSArANkPji7V8O3IRf6zMwsNiAGj3Hk",
|
|
23
|
-
"manifest.markdown.js": "sha384-
|
|
23
|
+
"manifest.markdown.js": "sha384-JWUxlUZADxk1hZY7NpDX051UhtXt/ofpfsCDBRcweU6JhLbN8B832yPLgCatxFdl",
|
|
24
24
|
"manifest.native.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
25
25
|
"manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
|
|
26
26
|
"manifest.resize.js": "sha384-+0IsyrGP/4gZmNbD6oU8dCgEyhpBsXMm74NIenYeUGuGRWyJDurcG27xE/CA0/aA",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
29
|
"manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
|
|
30
30
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
31
|
+
"manifest.sw.js": "sha384-oQvga1NtT0DZ6GU0aoy/zA1OAZIZc03cCpiEMOSA3z5S2lwR+QxcmJjpQqgxDKX/",
|
|
32
32
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
|
-
"manifest.text.edit.js": "sha384-
|
|
34
|
+
"manifest.text.edit.js": "sha384-RzOvLQE8GbmI6gntAwiotDewYT/08OHcd7sJ5hLon8e8u1Xbq1D7VfEGuaRVd2CE",
|
|
35
35
|
"manifest.toasts.js": "sha384-/OV32LLMJl0Iw8NoNQJ+5lgntLxjIJq/YNj5JAKS/+nUhVx4MMB9h1FOqP7zuj2M",
|
|
36
36
|
"manifest.tooltips.js": "sha384-uXVVQADl6pY2Pj9i/UaSclZN7dzbKhLkmevv7Ad0+wXR8LgqodALNFrc5jZJjlwm",
|
|
37
37
|
"manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -645,15 +645,21 @@ async function initializeMarkdownPlugin() {
|
|
|
645
645
|
|
|
646
646
|
// Track last processed content to prevent unnecessary re-renders
|
|
647
647
|
let lastProcessedContent = null;
|
|
648
|
+
// Baked output is protected from empty-value hides only until the
|
|
649
|
+
// first live render replaces it; after that, clearing the value
|
|
650
|
+
// hides the element like it always did.
|
|
651
|
+
let bakedOnly = hasBakedContent;
|
|
648
652
|
|
|
649
653
|
effect(() => {
|
|
650
654
|
getMarkdownContent(async (pathOrContent) => {
|
|
651
655
|
const mdDone = mdTrack();
|
|
652
656
|
try {
|
|
653
|
-
// Reset visibility if content is empty/undefined
|
|
657
|
+
// Reset visibility if content is empty/undefined. Baked
|
|
658
|
+
// content stays visible: a reactive expression can evaluate
|
|
659
|
+
// empty at boot (data still loading), and hiding then would
|
|
660
|
+
// blank prerendered output.
|
|
654
661
|
if (!pathOrContent || pathOrContent === undefined || pathOrContent === '') {
|
|
655
|
-
el.style.opacity = '0';
|
|
656
|
-
hasContent = false;
|
|
662
|
+
if (!bakedOnly) { el.style.opacity = '0'; hasContent = false; }
|
|
657
663
|
return;
|
|
658
664
|
}
|
|
659
665
|
|
|
@@ -726,8 +732,7 @@ async function initializeMarkdownPlugin() {
|
|
|
726
732
|
// Ensure we have a string (e.g. $route('path')?.content can be a proxy while loading)
|
|
727
733
|
const contentStr = typeof markdownContent === 'string' ? markdownContent : '';
|
|
728
734
|
if (!contentStr || contentStr.trim() === '') {
|
|
729
|
-
el.style.opacity = '0';
|
|
730
|
-
hasContent = false;
|
|
735
|
+
if (!bakedOnly) { el.style.opacity = '0'; hasContent = false; }
|
|
731
736
|
return;
|
|
732
737
|
}
|
|
733
738
|
|
|
@@ -770,6 +775,7 @@ async function initializeMarkdownPlugin() {
|
|
|
770
775
|
|
|
771
776
|
// Show content with fade-in
|
|
772
777
|
hasContent = true;
|
|
778
|
+
bakedOnly = false;
|
|
773
779
|
el.style.opacity = '1';
|
|
774
780
|
|
|
775
781
|
// Extract headings for anchor links
|