mnfst 0.5.218 → 0.5.219
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 +120 -46
- package/lib/manifest.css +83 -222
- package/lib/manifest.d.ts +2 -0
- package/lib/manifest.data.js +26 -3
- package/lib/manifest.defer.js +145 -34
- package/lib/manifest.edit.css +63 -27
- package/lib/manifest.edit.js +213 -28
- package/lib/manifest.integrity.json +11 -11
- package/lib/manifest.js +43 -2
- package/lib/manifest.markdown.js +11 -7
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.router.js +58 -69
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.text.edit.css +20 -0
- package/lib/manifest.text.edit.js +151 -25
- package/lib/manifest.utilities.js +246 -100
- package/lib/manifest.utilities.node.mjs +195 -35
- package/package.json +1 -1
package/lib/manifest.edit.js
CHANGED
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
// those deltas neither travel to the source nor survive in the overlay. They stay
|
|
48
48
|
// in the in-memory log, so undo still works for the session.
|
|
49
49
|
const authoringRegion = (r) => { const a = r != null && areaByKey(r); return !!(a && a._edit.authoring); };
|
|
50
|
-
const persistable = (d) => d.kind !== 'data-splice' && (d.
|
|
50
|
+
const persistable = (d) => d.kind !== 'data-splice' && (d.kind === 'st-move'
|
|
51
|
+
? authoringRegion(d.from) && authoringRegion(d.to)
|
|
52
|
+
: (d.region == null || authoringRegion(d.region)));
|
|
51
53
|
|
|
52
54
|
const saveState = () => { if (log.length > HISTORY_CAP) { const n = log.length - HISTORY_CAP; log.splice(0, n); cursor = Math.max(0, cursor - n); } const keep = log.filter(persistable); localStorage.setItem(LS_KEY, JSON.stringify({ v: SCHEMA, log: keep, cursor: Math.min(cursor, keep.length) })); };
|
|
53
55
|
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -100,6 +102,10 @@
|
|
|
100
102
|
}
|
|
101
103
|
const areas = () => [...editEls].filter(el => { for (let n = el.parentElement; n; n = n.parentElement) if (n._edit) return false; return true; });
|
|
102
104
|
const areaByKey = (k) => areas().find(a => a._edit.key === k);
|
|
105
|
+
const topArea = (a) => !!a && areas().includes(a); // only top-level regions can be move endpoints (nested ones can't persist)
|
|
106
|
+
// The top-level region an element belongs to NOW — resolved at commit time, not
|
|
107
|
+
// arm time, so a block moved to another region commits against its new home.
|
|
108
|
+
const regionOf = (el) => areas().find(a => a === el || a.contains(el));
|
|
103
109
|
const key = (area) => area._edit.key;
|
|
104
110
|
function editInfo(node) { for (let n = node; n; n = n.parentElement) if (n._edit) return n._edit; return null; }
|
|
105
111
|
const capOf = (node, cap) => { const i = editInfo(node); return !!i && !i.lock && i.caps.has(cap); };
|
|
@@ -114,7 +120,11 @@
|
|
|
114
120
|
}
|
|
115
121
|
function dataSourceExpr(area) { const t = area.querySelector('template[x-for]'); const m = t && t.getAttribute('x-for').match(/\bin\s+(.+)$/); return m ? m[1].trim() : null; }
|
|
116
122
|
const dataSourceName = (area) => { const e = dataSourceExpr(area); const m = e && e.match(/\$x\.(\w+)/); return m ? m[1] : (e || 'source'); };
|
|
117
|
-
|
|
123
|
+
// Plugin-injected nodes (size handles, drag ghosts) are not authored children —
|
|
124
|
+
// keys and paths skip them on client AND server (serve.mjs childAt mirrors this).
|
|
125
|
+
const pluginNode = (x) => x.hasAttribute('data-edit-handle') || x.hasAttribute('data-edit-ghost');
|
|
126
|
+
const realChildren = (el) => Array.from(el.children).filter(x => !pluginNode(x));
|
|
127
|
+
const sortableChildren = (c) => realChildren(c).filter(x => x.tagName !== 'TEMPLATE');
|
|
118
128
|
// Identity of a row in a data area. `data-key` is the explicit form; without it,
|
|
119
129
|
// fall back to the x-for's own :key so a plain list needs no extra attribute.
|
|
120
130
|
let _keyWarned = false;
|
|
@@ -243,18 +253,35 @@
|
|
|
243
253
|
// instead of being left holding the edit. (applyThemeState works the same way.)
|
|
244
254
|
function staticState() {
|
|
245
255
|
const node = {}, order = {}, sigs = {}, base = {}, html = {};
|
|
246
|
-
|
|
256
|
+
// An applied move re-addresses every EARLIER delta it carried along (its remap:
|
|
257
|
+
// the moved keys plus any twins whose ordinal shifted), so ops and baselines
|
|
258
|
+
// land on the element's current region/key instead of its pre-move address.
|
|
259
|
+
const moves = [];
|
|
260
|
+
for (let i = 0; i < cursor; i++) if (log[i].kind === 'st-move' && log[i].remap) moves.push({ i, remap: log[i].remap });
|
|
261
|
+
const addr = (i, region, path) => {
|
|
262
|
+
for (const m of moves) {
|
|
263
|
+
if (m.i <= i) continue;
|
|
264
|
+
const hit = m.remap.find(r => r[0] === region && r[1] === path);
|
|
265
|
+
if (hit) { region = hit[2]; path = hit[3]; }
|
|
266
|
+
}
|
|
267
|
+
return [region, path];
|
|
268
|
+
};
|
|
269
|
+
for (let i = 0; i < log.length; i++) {
|
|
270
|
+
const d = log[i];
|
|
247
271
|
if (d.kind === 'st-children' && d.html) Object.assign(html[d.region] = html[d.region] || {}, d.html);
|
|
272
|
+
if (d.kind === 'st-move' && d.html) { (html[d.from] = html[d.from] || {})[d.key] = d.html; (html[d.to] = html[d.to] || {})[d.toKey] = d.html; } // both sides can rebuild it
|
|
248
273
|
if (d.kind !== 'st-node') continue;
|
|
249
|
-
const
|
|
274
|
+
const [region, path] = addr(i, d.region, d.path);
|
|
275
|
+
const bk = region + '|' + path;
|
|
250
276
|
const b = base[bk] = base[bk] || {};
|
|
251
277
|
if (!(d.prop in b)) b[d.prop] = d.before;
|
|
252
278
|
if (d.sig) sigs[bk] = d.sig;
|
|
253
279
|
}
|
|
254
280
|
for (let i = 0; i < cursor; i++) {
|
|
255
281
|
const d = log[i];
|
|
256
|
-
if (d.kind === 'st-node') { (
|
|
282
|
+
if (d.kind === 'st-node') { const [region, path] = addr(i, d.region, d.path); (node[region] = node[region] || {}); (node[region][path] = node[region][path] || {})[d.prop] = d.value; }
|
|
257
283
|
else if (d.kind === 'st-order' || d.kind === 'st-children') order[d.region] = d.order;
|
|
284
|
+
else if (d.kind === 'st-move') { order[d.from] = d.fromOrder; order[d.to] = d.toOrder; }
|
|
258
285
|
}
|
|
259
286
|
// The markup for anything added or removed comes from the WHOLE log, the same
|
|
260
287
|
// way baselines do — undoing a delete has to be able to rebuild the element,
|
|
@@ -329,8 +356,10 @@
|
|
|
329
356
|
kids.forEach((el, i) => { by[keys[i]] = el; });
|
|
330
357
|
const markup = html[region] || {};
|
|
331
358
|
const next = want.map(kk => by[kk] || materialize(markup[kk], kk)).filter(Boolean);
|
|
359
|
+
// Reinsert at the managed range — never scramble real children past plugin handles.
|
|
360
|
+
const anchor = kids.length ? kids[kids.length - 1].nextSibling : Array.from(area.children).find(pluginNode) || null;
|
|
332
361
|
kids.forEach(el => { if (!next.includes(el)) el.remove(); });
|
|
333
|
-
next.forEach(el => area.
|
|
362
|
+
next.forEach(el => area.insertBefore(el, anchor));
|
|
334
363
|
}
|
|
335
364
|
});
|
|
336
365
|
}
|
|
@@ -382,6 +411,62 @@
|
|
|
382
411
|
if (eq(before, order)) return;
|
|
383
412
|
log.splice(cursor); log.push({ kind: 'st-order', region, order, before }); cursor = log.length; lastOrder[region] = order; saveState(); refresh();
|
|
384
413
|
}
|
|
414
|
+
// Positional re-key of a region: give every element the key a FRESH session would
|
|
415
|
+
// derive (markStatic document order, ordinals by position among same-base keys).
|
|
416
|
+
// That is what keeps client, server, and reload addressing the same elements after
|
|
417
|
+
// a move shifts membership. Returns the renames as [oldKey, newKey] pairs.
|
|
418
|
+
function rekeyRegion(area) {
|
|
419
|
+
const seen = Object.create(null), changes = [];
|
|
420
|
+
for (const el of [area, ...area.querySelectorAll('*')]) {
|
|
421
|
+
if (el.hasAttribute('data-edit-handle') || el.hasAttribute('data-edit-ghost')) continue;
|
|
422
|
+
if (el.parentElement && el.parentElement.closest('[data-text-edit]')) continue;
|
|
423
|
+
const cur = el.getAttribute('data-edit-key');
|
|
424
|
+
const base = cur ? cur.split('#')[0] : staticKey(el);
|
|
425
|
+
const n = seen[base] = (seen[base] || 0) + 1;
|
|
426
|
+
const k = n > 1 ? base + '#' + n : base;
|
|
427
|
+
if (cur !== k) { el.setAttribute('data-edit-key', k); if (cur) changes.push([cur, k]); }
|
|
428
|
+
}
|
|
429
|
+
return changes;
|
|
430
|
+
}
|
|
431
|
+
// Replay/undo/redo of a move's key renames. Resolve-then-set so swapped ordinals
|
|
432
|
+
// never collide mid-application; the moved element itself (region change) is
|
|
433
|
+
// handled by applyStaticState's materialization, not here.
|
|
434
|
+
function applyMoveKeys(d, invert) {
|
|
435
|
+
if (!d.remap) return;
|
|
436
|
+
const jobs = [];
|
|
437
|
+
for (const [r, o, r2, n] of d.remap) {
|
|
438
|
+
if (r !== r2) continue;
|
|
439
|
+
const a = areaByKey(r); if (!a) continue;
|
|
440
|
+
const findKey = invert ? n : o, setKey = invert ? o : n;
|
|
441
|
+
const el = [a, ...a.querySelectorAll('[data-edit-key]')].find(e => e.getAttribute('data-edit-key') === findKey);
|
|
442
|
+
if (el) jobs.push([el, setKey]);
|
|
443
|
+
}
|
|
444
|
+
jobs.forEach(([el, k]) => el.setAttribute('data-edit-key', k));
|
|
445
|
+
}
|
|
446
|
+
// Cross-region move (static → static): one delta carries both region orders, so a
|
|
447
|
+
// single undo puts the block back. The item must already sit in its new parent.
|
|
448
|
+
// Both regions are re-keyed positionally and every rename travels on the delta
|
|
449
|
+
// (remap), so earlier deltas on the moved block — and on ordinal-shifted twins —
|
|
450
|
+
// keep addressing the right elements.
|
|
451
|
+
function commitMove(fromArea, toArea, item) {
|
|
452
|
+
const from = key(fromArea), to = key(toArea), fromKey = item.getAttribute('data-edit-key') || staticKey(item);
|
|
453
|
+
const descOld = [...item.querySelectorAll('[data-edit-key]')].map(n => [n, n.getAttribute('data-edit-key')]);
|
|
454
|
+
item.removeAttribute('data-edit-key');
|
|
455
|
+
item.querySelectorAll('[data-edit-key]').forEach(n => n.removeAttribute('data-edit-key')); // arrival re-keys in the new region
|
|
456
|
+
const fromPairs = rekeyRegion(fromArea).map(([o, n]) => [from, o, from, n]);
|
|
457
|
+
const toPairs = rekeyRegion(toArea).map(([o, n]) => [to, o, to, n]);
|
|
458
|
+
const toKey = item.getAttribute('data-edit-key');
|
|
459
|
+
const remap = [[from, fromKey, to, toKey]];
|
|
460
|
+
descOld.forEach(([n, o]) => { const nk = n.getAttribute('data-edit-key'); if (nk) remap.push([from, o, to, nk]); });
|
|
461
|
+
remap.push(...fromPairs, ...toPairs);
|
|
462
|
+
const html = blockHTML(item);
|
|
463
|
+
log.splice(cursor);
|
|
464
|
+
log.push({ kind: 'st-move', from, to, key: fromKey, toKey, fromOrder: staticKeys(fromArea), toOrder: staticKeys(toArea), html, remap });
|
|
465
|
+
cursor = log.length;
|
|
466
|
+
lastOrder[from] = staticKeys(fromArea); lastOrder[to] = staticKeys(toArea);
|
|
467
|
+
markStatic(fromArea); armArea(toArea); // paths shift on both sides; the arrival needs baselines + affordances
|
|
468
|
+
saveState(); refresh();
|
|
469
|
+
}
|
|
385
470
|
|
|
386
471
|
/* ---- Commit / undo / redo (data area snapshots) ---- */
|
|
387
472
|
function commit(area) {
|
|
@@ -394,6 +479,7 @@
|
|
|
394
479
|
const dispatchApply = (d) => { if (d.kind === 'cmp-main' || d.kind === 'cmp-inst') applyComponentState(); else if (d.kind === 'data-val') applyDataValues(); else if (d.kind === 'theme') applyThemeState(); else applyStaticState(); };
|
|
395
480
|
async function undo() {
|
|
396
481
|
if (cursor === 0) return; const d = log[--cursor];
|
|
482
|
+
if (d.kind === 'st-move') applyMoveKeys(d, true); // ordinal renames roll back before state re-applies
|
|
397
483
|
if (d.kind === 'data-splice') applySplice(d, true);
|
|
398
484
|
else if (d.kind === 'data') { await applySnap(areaByKey(d.region), d.kind, d.before); lastSnap[d.region] = d.before; }
|
|
399
485
|
else dispatchApply(d);
|
|
@@ -401,6 +487,7 @@
|
|
|
401
487
|
}
|
|
402
488
|
async function redo() {
|
|
403
489
|
if (cursor >= log.length) return; const d = log[cursor++];
|
|
490
|
+
if (d.kind === 'st-move') applyMoveKeys(d, false);
|
|
404
491
|
if (d.kind === 'data-splice') applySplice(d, false);
|
|
405
492
|
else if (d.kind === 'data') { await applySnap(areaByKey(d.region), d.kind, d.after); lastSnap[d.region] = d.after; }
|
|
406
493
|
else dispatchApply(d);
|
|
@@ -444,7 +531,12 @@
|
|
|
444
531
|
return el && el.parentElement === area ? el : null;
|
|
445
532
|
}
|
|
446
533
|
|
|
447
|
-
|
|
534
|
+
// The region a block BELONGS TO — from the parent, so a block that is itself an
|
|
535
|
+
// x-edit region resolves to its outer region, never to itself.
|
|
536
|
+
const blockArea = (el) => el && el.parentElement && el.parentElement.closest('[data-edit-area]');
|
|
537
|
+
// A block that is itself an x-edit region owns its own delta addressing; moving
|
|
538
|
+
// it across regions is refused (reordering it within its parent still works).
|
|
539
|
+
const isRegionBlock = (el) => !!el && (!!el._edit || el.hasAttribute('data-edit-area'));
|
|
448
540
|
|
|
449
541
|
// Markup without the plugin's own affordances, so a duplicate re-arms cleanly
|
|
450
542
|
// rather than inheriting half-initialised state from its original.
|
|
@@ -488,6 +580,7 @@
|
|
|
488
580
|
const block = el || target;
|
|
489
581
|
if (op === 'paste') return !!clipboard && !!(block ? blockArea(block) : areas().find(isActive));
|
|
490
582
|
if (!block || locked(block)) return false;
|
|
583
|
+
if (op === 'move' && isRegionBlock(block)) return false;
|
|
491
584
|
const area = blockArea(block);
|
|
492
585
|
if (!area || !isActive(area)) return false;
|
|
493
586
|
if (classify(area) === 'component') return false; // instances are overridden, not restructured
|
|
@@ -496,7 +589,9 @@
|
|
|
496
589
|
|
|
497
590
|
function copyBlock(el) {
|
|
498
591
|
const block = el || target; if (!block) return false;
|
|
499
|
-
|
|
592
|
+
const area = blockArea(block);
|
|
593
|
+
if (!area) return false; // a whole region is a container, not a copyable block
|
|
594
|
+
clipboard = { html: blockHTML(block), source: key(area) };
|
|
500
595
|
bump();
|
|
501
596
|
return true;
|
|
502
597
|
}
|
|
@@ -542,6 +637,23 @@
|
|
|
542
637
|
|
|
543
638
|
const cutBlock = (el) => copyBlock(el) && removeBlock(el);
|
|
544
639
|
|
|
640
|
+
// Programmatic cross-region move (what a pointer drag commits): insert the block
|
|
641
|
+
// into `dest` (area element or x-edit key) before `ref`, or at the end.
|
|
642
|
+
function moveBlock(el, dest, ref) {
|
|
643
|
+
const block = el ? blockOf(el) || el : target; if (!block || locked(block) || isRegionBlock(block)) return false;
|
|
644
|
+
const from = blockArea(block);
|
|
645
|
+
const to = typeof dest === 'string' ? areaByKey(dest) : dest;
|
|
646
|
+
if (!from || !to || to === from || !to._edit) return false;
|
|
647
|
+
if (!topArea(from) || !topArea(to)) return false; // a nested region can't persist a move — refuse, never drop it silently
|
|
648
|
+
if (!isActive(from) || !isActive(to) || locked(to)) return false;
|
|
649
|
+
if (classify(from) !== 'static' || classify(to) !== 'static' || !capOf(to, 'sort')) return false;
|
|
650
|
+
if (block === to || block.contains(to)) return false;
|
|
651
|
+
if (ref && ref.parentElement !== to) ref = null;
|
|
652
|
+
if (ref) to.insertBefore(block, ref); else to.appendChild(block);
|
|
653
|
+
commitMove(from, to, block);
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
|
|
545
657
|
function pasteBlock(el) {
|
|
546
658
|
if (!clipboard) return false;
|
|
547
659
|
const block = el || target;
|
|
@@ -645,10 +757,13 @@
|
|
|
645
757
|
}
|
|
646
758
|
}
|
|
647
759
|
function onPointerDown(e) {
|
|
648
|
-
const item = this, area = item.closest('[data-edit-area]');
|
|
760
|
+
const item = this, area = item.parentElement && item.parentElement.closest('[data-edit-area]'); // origin = the OUTER region, never the item itself
|
|
649
761
|
if (!isActive(area) || (e.pointerType === 'mouse' && e.button !== 0)) return;
|
|
650
762
|
if (e.target.isContentEditable || e.target.hasAttribute('data-edit-handle')) return; // text/size win
|
|
763
|
+
if (e._editDrag) return; e._editDrag = true; // one drag per gesture — the innermost sortable item claims it
|
|
651
764
|
const container = item.parentElement;
|
|
765
|
+
const crossOK = classify(area) === 'static' && !isRegionBlock(item) && topArea(area); // cross-region: top-level static → static only; a region-block only reorders in place
|
|
766
|
+
let hovered = container;
|
|
652
767
|
const homeNext = item.nextElementSibling; // where to put it back if cancelled
|
|
653
768
|
const start = item.getBoundingClientRect();
|
|
654
769
|
const preStyle = item.getAttribute('style');
|
|
@@ -694,7 +809,13 @@
|
|
|
694
809
|
active = true; lift(ev); paint();
|
|
695
810
|
}
|
|
696
811
|
ev.preventDefault();
|
|
697
|
-
|
|
812
|
+
const over = (crossOK && dropArea(px, py)) || ghost.parentElement; // cross-region drop search
|
|
813
|
+
if (over !== hovered) {
|
|
814
|
+
if (hovered !== container) hovered.removeAttribute('data-edit-dragging-in');
|
|
815
|
+
hovered = over;
|
|
816
|
+
if (hovered !== container) hovered.setAttribute('data-edit-dragging-in', '');
|
|
817
|
+
}
|
|
818
|
+
reorderOver(over, px, py);
|
|
698
819
|
if (!frame) frame = requestAnimationFrame(paint);
|
|
699
820
|
};
|
|
700
821
|
|
|
@@ -711,8 +832,17 @@
|
|
|
711
832
|
if (preStyle == null) item.removeAttribute('style'); else item.setAttribute('style', preStyle);
|
|
712
833
|
item.removeAttribute('data-edit-dragging');
|
|
713
834
|
area.removeAttribute('data-edit-dragging-in');
|
|
835
|
+
if (hovered !== container) hovered.removeAttribute('data-edit-dragging-in');
|
|
714
836
|
ghost = null; dragged = null;
|
|
715
|
-
if (cancelled) announce('Cancelled');
|
|
837
|
+
if (cancelled) { announce('Cancelled'); return; }
|
|
838
|
+
const dest = item.parentElement;
|
|
839
|
+
if (crossOK && dest !== container && dest._edit) {
|
|
840
|
+
// Re-validate at drop: a region locked or deactivated mid-drag
|
|
841
|
+
// (reached through the ghost fallback) must not take the block.
|
|
842
|
+
if (isActive(dest) && !locked(dest) && capOf(dest, 'sort') && topArea(dest)) { commitMove(area, dest, item); announce('Moved to ' + key(dest)); }
|
|
843
|
+
else { if (homeNext) container.insertBefore(item, homeNext); else container.appendChild(item); announce('Cancelled'); }
|
|
844
|
+
}
|
|
845
|
+
else finishReorder(area);
|
|
716
846
|
};
|
|
717
847
|
const onUp = () => settle(false);
|
|
718
848
|
// Escape puts it back, the way every drag is expected to be escapable.
|
|
@@ -722,11 +852,19 @@
|
|
|
722
852
|
document.addEventListener('keydown', onKey, true);
|
|
723
853
|
}
|
|
724
854
|
function reorderOver(container, x, y) {
|
|
725
|
-
if (!dragged
|
|
855
|
+
if (!dragged) return;
|
|
726
856
|
const ref = afterElement(container, x, y);
|
|
727
|
-
if (ref === dragged || ref === dragged.nextElementSibling) return;
|
|
857
|
+
if (dragged.parentElement === container && (ref === dragged || ref === dragged.nextElementSibling)) return;
|
|
728
858
|
if (ref == null) container.appendChild(dragged); else container.insertBefore(dragged, ref);
|
|
729
859
|
}
|
|
860
|
+
// Cross-region drop target: the innermost active static sort region under the pointer.
|
|
861
|
+
function dropArea(x, y) {
|
|
862
|
+
const hit = document.elementFromPoint ? document.elementFromPoint(x, y) : null;
|
|
863
|
+
for (let a = hit && hit.closest ? hit.closest('[data-edit-area]') : null; a; a = a.parentElement && a.parentElement.closest('[data-edit-area]')) {
|
|
864
|
+
if (a._edit && isActive(a) && !locked(a) && capOf(a, 'sort') && classify(a) === 'static' && topArea(a)) return a;
|
|
865
|
+
}
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
730
868
|
function finishReorder(area) { if (classify(area) === 'data') { applyDataOrder(area, sortableChildren(area).map(c => itemKey(c, area))); commit(area); } else commitStaticOrder(area); }
|
|
731
869
|
// Reading-order insertion: block, inline, flex row/col/wrap, grid auto-flow.
|
|
732
870
|
// The FIRST sibling past the pointer, not the nearest — nearest can pick an
|
|
@@ -899,6 +1037,15 @@
|
|
|
899
1037
|
|
|
900
1038
|
|
|
901
1039
|
/* ---- Inline text editing ---- */
|
|
1040
|
+
// Focusing a text leaf selects its content, so the common case — replace the
|
|
1041
|
+
// text — is one click and type. Deferred a frame so it wins over the click's
|
|
1042
|
+
// own caret placement; a second click still collapses to a caret for fine edits.
|
|
1043
|
+
function selectAllIn(el) {
|
|
1044
|
+
requestAnimationFrame(() => {
|
|
1045
|
+
if (document.activeElement !== el) return;
|
|
1046
|
+
try { const r = document.createRange(); r.selectNodeContents(el); const s = window.getSelection(); s.removeAllRanges(); s.addRange(r); } catch { }
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
902
1049
|
// Where x-text-edit is declared, it owns the element: this plugin neither makes
|
|
903
1050
|
// its leaves contenteditable nor competes for the caret. If the rich editor has
|
|
904
1051
|
// no expression of its own, we capture what it produces as an ordinary text
|
|
@@ -909,8 +1056,9 @@
|
|
|
909
1056
|
if (el._richBound) return; el._richBound = true;
|
|
910
1057
|
el.addEventListener('focusin', () => { el._preEdit = el.innerHTML.trim(); });
|
|
911
1058
|
el.addEventListener('blur', () => {
|
|
912
|
-
const
|
|
913
|
-
commit(
|
|
1059
|
+
const a = regionOf(el) || area; // the region the element sits in NOW, not where it was armed
|
|
1060
|
+
const commit = classify(a) === 'component' ? commitComponentNode : commitStaticNode;
|
|
1061
|
+
commit(a, el, 'text', el.innerHTML.trim());
|
|
914
1062
|
}, true);
|
|
915
1063
|
});
|
|
916
1064
|
}
|
|
@@ -924,8 +1072,8 @@
|
|
|
924
1072
|
if (!capOf(el, 'text') || el.hasAttribute('x-text') || el.hasAttribute('x-html')) return;
|
|
925
1073
|
el.setAttribute('contenteditable', 'true');
|
|
926
1074
|
if (el._textBound) return; el._textBound = true;
|
|
927
|
-
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim(); const d = el.closest('[draggable="true"]'); if (d) d.setAttribute('draggable', 'false'); });
|
|
928
|
-
el.addEventListener('blur', () => { const d = el.closest('[data-edit-area] [draggable="false"]'); if (d) d.setAttribute('draggable', 'true'); commitStaticNode(area, el, 'text', el.innerHTML.trim()); });
|
|
1075
|
+
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim(); const d = el.closest('[draggable="true"]'); if (d) d.setAttribute('draggable', 'false'); selectAllIn(el); });
|
|
1076
|
+
el.addEventListener('blur', () => { const d = el.closest('[data-edit-area] [draggable="false"]'); if (d) d.setAttribute('draggable', 'true'); commitStaticNode(regionOf(el) || area, el, 'text', el.innerHTML.trim()); }); // live region: the element may have moved since arming
|
|
929
1077
|
});
|
|
930
1078
|
}
|
|
931
1079
|
|
|
@@ -957,7 +1105,7 @@
|
|
|
957
1105
|
if (!el.hasAttribute('data-text-edit')) el.setAttribute('contenteditable', 'true');
|
|
958
1106
|
if (el._dvBound) return; el._dvBound = true;
|
|
959
1107
|
const read = () => rich ? el.innerHTML.trim() : el.textContent;
|
|
960
|
-
el.addEventListener('focus', () => { el._preEdit = read(); });
|
|
1108
|
+
el.addEventListener('focus', () => { el._preEdit = read(); selectAllIn(el); });
|
|
961
1109
|
el.addEventListener('focusin', () => { el._preEdit = read(); });
|
|
962
1110
|
el.addEventListener('blur', () => commitDataValue(area, source, id, field, read(), el), true);
|
|
963
1111
|
});
|
|
@@ -967,8 +1115,9 @@
|
|
|
967
1115
|
/* ---- COMPONENT editing: text leaves addressed by structural path. Right-click an
|
|
968
1116
|
instance for the scope (this instance vs all) + per-element classes + revert. ---- */
|
|
969
1117
|
const componentName = (area) => { const r = area.querySelector('[data-component]'); return (r?.getAttribute('data-component') || '').replace(/-\d+$/, ''); };
|
|
970
|
-
|
|
971
|
-
function
|
|
1118
|
+
// Paths index authored children only (realChildren): plugin-injected nodes never count.
|
|
1119
|
+
function pathOf(node, root) { const idx = []; let n = node; while (n && n !== root && n.parentElement) { idx.unshift(realChildren(n.parentElement).indexOf(n)); n = n.parentElement; } return idx.join('.'); }
|
|
1120
|
+
function nodeByPath(root, path) { let el = root; for (const i of path.split('.').map(Number)) { el = realChildren(el)[i]; if (!el) return null; } return el; }
|
|
972
1121
|
// While editing in 'All' scope, mirror the edit to every OTHER instance live — but
|
|
973
1122
|
// skip the source element (don't fight the caret) and skip any instance that has its
|
|
974
1123
|
// OWN committed override for this node/prop (instance overrides always win).
|
|
@@ -1002,7 +1151,7 @@
|
|
|
1002
1151
|
if (!(p in area._baseText)) area._baseText[p] = el.innerHTML.trim();
|
|
1003
1152
|
el.setAttribute('contenteditable', 'true');
|
|
1004
1153
|
if (el._textBound) return; el._textBound = true;
|
|
1005
|
-
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim(); });
|
|
1154
|
+
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim(); selectAllIn(el); });
|
|
1006
1155
|
el.addEventListener('input', () => liveMainPropagate(area, el, 'text', el.innerHTML));
|
|
1007
1156
|
el.addEventListener('blur', () => commitComponentNode(area, el, 'text', el.innerHTML.trim()));
|
|
1008
1157
|
});
|
|
@@ -1202,6 +1351,10 @@
|
|
|
1202
1351
|
// being edited. The attributes are all CSS asks for — what is shown, and
|
|
1203
1352
|
// whether it is shown at all, is decided in the stylesheet.
|
|
1204
1353
|
area.toggleAttribute('data-edit-authoring', !!area._edit.authoring);
|
|
1354
|
+
// .authoring regions get the UI scope by default; anything else opts in
|
|
1355
|
+
// by putting data-edit-ui on the region or an ancestor. Only remove what
|
|
1356
|
+
// the plugin itself added, never an author's attribute.
|
|
1357
|
+
if (area._edit.authoring && !area.hasAttribute('data-edit-ui')) { area.setAttribute('data-edit-ui', ''); area._uiByPlugin = true; }
|
|
1205
1358
|
area.setAttribute('data-edit-label', kind === 'component' ? `${key(area)} · component · ${area._editScope || 'instance'}` : `${key(area)} · ${kind}`);
|
|
1206
1359
|
if (kind === 'static') markStatic(area);
|
|
1207
1360
|
[area, ...area.querySelectorAll('[data-edit-area]')].forEach(c => { if (capOf(c, 'sort') && !locked(c)) makeSortable(c); });
|
|
@@ -1228,6 +1381,7 @@
|
|
|
1228
1381
|
area.querySelectorAll('[data-edit-sortable]').forEach(e => { ['data-edit-sortable', 'data-edit-grabbed', 'data-edit-dragging', 'tabindex', 'role', 'draggable'].forEach(a => e.removeAttribute(a)); e._dragBound = false; });
|
|
1229
1382
|
[area, ...area.querySelectorAll('[data-edit-sizable],[data-edit-movable]')].forEach(e => { e.removeAttribute('data-edit-sizable'); e.removeAttribute('data-edit-movable'); e._sizeBound = false; });
|
|
1230
1383
|
area.removeAttribute('data-edit-armed'); area.removeAttribute('data-edit-label');
|
|
1384
|
+
if (area._uiByPlugin) { area.removeAttribute('data-edit-ui'); area._uiByPlugin = false; }
|
|
1231
1385
|
}
|
|
1232
1386
|
|
|
1233
1387
|
/* ---- Activation: per-area, always-on by default (.gated needs $edit.on()) ---- */
|
|
@@ -1263,24 +1417,44 @@
|
|
|
1263
1417
|
// already auto-persists to localStorage on commit).
|
|
1264
1418
|
function buildPatches() {
|
|
1265
1419
|
const patches = Object.entries(fold()).filter(([k]) => authoringRegion(k)).map(([k, v]) => patchFor(k, v.kind, v.snap)); // data
|
|
1266
|
-
const toEdits = (paths, area) => {
|
|
1420
|
+
const toEdits = (paths, area, fileOrder) => {
|
|
1267
1421
|
const e = [];
|
|
1268
1422
|
Object.entries(paths).forEach(([k, props]) => {
|
|
1269
1423
|
// Keys address across a session; the source writer navigates by
|
|
1270
|
-
// position, so resolve to where the element actually sits now
|
|
1271
|
-
//
|
|
1424
|
+
// position, so resolve to where the element actually sits now (the
|
|
1425
|
+
// server applies order before edits, so current position is the
|
|
1426
|
+
// right one). A deleted element has nothing to write.
|
|
1272
1427
|
const el = area && (area.getAttribute('data-edit-key') === k ? area : area.querySelector(`[data-edit-key="${CSS.escape(k)}"]`));
|
|
1273
|
-
|
|
1428
|
+
let path = el ? pathOf(el, area) : k;
|
|
1274
1429
|
if (area && !el) return;
|
|
1430
|
+
// RULE: paths must be valid against the file state the server will reach —
|
|
1431
|
+
// a pending st-children change can't be applied there (a short order is
|
|
1432
|
+
// skipped), so address by the block's position in the UNMODIFIED file.
|
|
1433
|
+
if (el && el !== area && fileOrder) {
|
|
1434
|
+
let block = el; while (block.parentElement && block.parentElement !== area) block = block.parentElement;
|
|
1435
|
+
const fi = fileOrder.indexOf(block.getAttribute('data-edit-key'));
|
|
1436
|
+
if (fi < 0) return; // block not in the file (added this session) — nothing to address
|
|
1437
|
+
const segs = path.split('.'); segs[0] = String(fi); path = segs.join('.');
|
|
1438
|
+
}
|
|
1275
1439
|
Object.entries(props).forEach(([prop, value]) => e.push({ path, key: k, prop, value }));
|
|
1276
1440
|
});
|
|
1277
1441
|
return e;
|
|
1278
1442
|
};
|
|
1443
|
+
for (let i = 0; i < cursor; i++) { // cross-region moves travel in log order, ahead of per-region ops
|
|
1444
|
+
const d = log[i];
|
|
1445
|
+
if (d.kind !== 'st-move' || !authoringRegion(d.from) || !authoringRegion(d.to)) continue;
|
|
1446
|
+
patches.push({ kind: 'static-move', from: d.from, to: d.to, key: d.key, toKey: d.toKey, toOrder: d.toOrder });
|
|
1447
|
+
}
|
|
1279
1448
|
const ss = staticState(); // static: per-node ops + reorder permutation (no whole HTML)
|
|
1280
1449
|
new Set([...Object.keys(ss.node), ...Object.keys(ss.order)]).forEach(region => {
|
|
1281
1450
|
if (!authoringRegion(region)) return;
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1451
|
+
const area = areaByKey(region);
|
|
1452
|
+
const structural = log.slice(0, cursor).some(d => d.kind === 'st-children' && d.region === region);
|
|
1453
|
+
const edits = toEdits(ss.node[region] || {}, area, structural ? (area && area._baseOrder) || [] : null);
|
|
1454
|
+
// A moved region always gets its folded order (pre-move reorders are NOT
|
|
1455
|
+
// in the move splice); the server skips a reorder that is already true.
|
|
1456
|
+
const order = ss.order[region] || null;
|
|
1457
|
+
if (edits.length || order) patches.push({ kind: 'static', region, edits, order });
|
|
1284
1458
|
});
|
|
1285
1459
|
const dv = dataValueState(); // data-value edits → field writes (local file / cloud $update)
|
|
1286
1460
|
Object.entries(dv).forEach(([source, recs]) => Object.entries(recs).forEach(([id, fields]) => Object.entries(fields).forEach(([field, value]) => patches.push({ kind: 'data-val', source, id, field, value }))));
|
|
@@ -1353,7 +1527,17 @@
|
|
|
1353
1527
|
function restore() {
|
|
1354
1528
|
if (!log.length) return;
|
|
1355
1529
|
for (const [k, v] of Object.entries(fold())) { const area = areaByKey(k); if (area && v.kind === 'data') waitForData(area, () => applySnap(area, 'data', v.snap)); }
|
|
1356
|
-
|
|
1530
|
+
// A replayed move must first converge live keys to the recorded ones (ordinal
|
|
1531
|
+
// renames from collisions). Skipped when the source already holds the move
|
|
1532
|
+
// (post-save reload): fresh derivation then matches the recorded order.
|
|
1533
|
+
for (let i = 0; i < cursor; i++) {
|
|
1534
|
+
const d = log[i];
|
|
1535
|
+
if (d.kind !== 'st-move' || !d.remap) continue;
|
|
1536
|
+
const ta = areaByKey(d.to);
|
|
1537
|
+
if (!ta || eq(staticKeys(ta), d.toOrder)) continue;
|
|
1538
|
+
applyMoveKeys(d, false);
|
|
1539
|
+
}
|
|
1540
|
+
if (log.some(d => d.kind === 'st-node' || d.kind === 'st-order' || d.kind === 'st-move')) applyStaticState();
|
|
1357
1541
|
if (log.some(d => d.kind === 'cmp-main' || d.kind === 'cmp-inst')) applyComponentState();
|
|
1358
1542
|
if (log.some(d => d.kind === 'data-val')) { const da = areas().find(a => classify(a) === 'data'); if (da) waitForData(da, applyDataValues); }
|
|
1359
1543
|
if (log.some(d => d.kind === 'theme')) applyThemeState();
|
|
@@ -1383,6 +1567,7 @@
|
|
|
1383
1567
|
paste(el) { return pasteBlock(el); },
|
|
1384
1568
|
duplicate(el) { return duplicateBlock(el); },
|
|
1385
1569
|
remove(el) { return removeBlock(el); },
|
|
1570
|
+
move(el, dest, before) { return moveBlock(el, dest, before); },
|
|
1386
1571
|
block(node) { return blockOf(node); },
|
|
1387
1572
|
patches() { return buildPatches(); }, // resolved B-side source patches
|
|
1388
1573
|
export() { return JSON.parse(JSON.stringify({ log, cursor })); } // A-side overlay (e.g. push to Appwrite)
|
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-X30RFvbwT9qEIaMsMUumnW54ZzcnY5aY9QQeXPG8/0sI6VAbAw3+FZ6iuZb6b4cu",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-K0TiCzUFBML9Vf/deb/hrzMGQVpN5OZRcMNA/oUIkvmsmuwnVs/Vl+CiEkIxBswM",
|
|
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-5Ki7YogsJza+eVpEU9L8wq/WwdBFIbXzrc3+TFgiDB9skA1V+EW9Tnse8gkxSyej",
|
|
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-jXsslu3l0d7Sw2M3XEbiD8bBLM7bX1tzlqYCNgMJeKhIx3DGRl1UKNd2WKwKxfr9",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
|
-
"manifest.defer.js": "sha384-
|
|
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-X3FVmNQvDMV8RqVYxAt1TDb6BTsf/qiWOPnFEM1L67m1LpeW7K+m9mCgOO8TsFPB",
|
|
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-Wix51VpAtzCPo43dM6eHa633+W/Xtn/xRN4QBSK8Hx7s4fP1kequ8ecaSMyacNvG",
|
|
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",
|
|
27
|
-
"manifest.router.js": "sha384-
|
|
27
|
+
"manifest.router.js": "sha384-xkQaHO60lMQwtnqGPxPJJ646eyqBjINs4AFesrQbe2dWqPIoIcJ9RD5wsjqjak7w",
|
|
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-ZHAcZayIy8b/fuajad/c6RA3zNLMjxb7Gw+JAIKJz/N5PvFBvk9M8o1XOAMWW9da",
|
|
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-AZsUx2nwCoEUl1SxFkuf6WjskKc70ve20tsS47oVxlGSRH+Zy0ICEx5cCMY2n7d8",
|
|
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",
|
|
38
|
-
"manifest.utilities.js": "sha384-
|
|
38
|
+
"manifest.utilities.js": "sha384-L5uXMLFBIF6LUM1haRjd4eGjHSWuqsCYSpPZRPYosNfJsMC5U5TnmjBxIhrZRe2d",
|
|
39
39
|
"manifest.virtual.js": "sha384-ZDNoF9QVhVcVpkjIK6NH15S6I5c6OBshiJpXSOYjNsIB+298I0a7g3aFO+jyq4Vb",
|
|
40
|
-
"manifest.js": "sha384-
|
|
40
|
+
"manifest.js": "sha384-CyrcEV95hOBffjZUh4GaqktqzEp0oHK9F4xgbMKt4OuoNbCj41C6eNEfv7YM7Ho/"
|
|
41
41
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -127,19 +127,61 @@
|
|
|
127
127
|
* `_x_currentIfEl` (avoids re-rendering heavy content); x-for clones are removed.
|
|
128
128
|
* Runs on alpine:init so a page without Alpine keeps its baked content.
|
|
129
129
|
*/
|
|
130
|
+
function adoptIfClone(tpl, el) {
|
|
131
|
+
const A = window.Alpine;
|
|
132
|
+
// Mirrors Alpine's x-if show() bookkeeping.
|
|
133
|
+
const undo = () => {
|
|
134
|
+
A.mutateDom(() => {
|
|
135
|
+
A.destroyTree(el);
|
|
136
|
+
el.remove();
|
|
137
|
+
});
|
|
138
|
+
delete tpl._x_currentIfEl;
|
|
139
|
+
delete tpl._x_lastRenderedEl;
|
|
140
|
+
};
|
|
141
|
+
// Boot-time false (e.g. data still loading) keeps the clone until
|
|
142
|
+
// manifest:ready, then removes it only if still false — no blank frame.
|
|
143
|
+
let pending = false;
|
|
144
|
+
const adoptedUndo = () => {
|
|
145
|
+
if (window.__manifestReady || !window.__manifestReadyCoordinator) return undo();
|
|
146
|
+
queueMicrotask(() => { if (tpl._x_currentIfEl === el) tpl._x_undoIf = adoptedUndo; });
|
|
147
|
+
if (pending) return;
|
|
148
|
+
pending = true;
|
|
149
|
+
window.addEventListener('manifest:ready', () => {
|
|
150
|
+
if (tpl._x_currentIfEl !== el) return;
|
|
151
|
+
if (!tpl.isConnected || !A.evaluate(tpl, tpl.getAttribute('x-if'))) {
|
|
152
|
+
undo();
|
|
153
|
+
delete tpl._x_undoIf;
|
|
154
|
+
}
|
|
155
|
+
}, { once: true });
|
|
156
|
+
};
|
|
157
|
+
tpl._x_currentIfEl = el;
|
|
158
|
+
tpl._x_lastRenderedEl = el;
|
|
159
|
+
tpl._x_undoIf = adoptedUndo;
|
|
160
|
+
}
|
|
130
161
|
function reconcilePrerenderClones() {
|
|
131
162
|
if (typeof document === 'undefined' || !document.querySelectorAll) return;
|
|
163
|
+
const adopted = [];
|
|
132
164
|
document.querySelectorAll('[data-mnfst-prerender-clone]').forEach((el) => {
|
|
133
165
|
if (el.closest && el.closest('[data-hydrate]')) return;
|
|
134
166
|
el.removeAttribute('data-mnfst-prerender-clone');
|
|
135
167
|
const tpl = el.previousElementSibling;
|
|
136
168
|
if (tpl && tpl.tagName === 'TEMPLATE' && tpl.hasAttribute('x-if')) {
|
|
137
169
|
// Alpine's x-if show() returns early when _x_currentIfEl is set.
|
|
138
|
-
tpl
|
|
170
|
+
adoptIfClone(tpl, el);
|
|
171
|
+
adopted.push([tpl, el]);
|
|
139
172
|
} else {
|
|
140
173
|
el.remove();
|
|
141
174
|
}
|
|
142
175
|
});
|
|
176
|
+
if (!adopted.length) return;
|
|
177
|
+
// Clones the start walk missed: init once (initTree skips marked nodes).
|
|
178
|
+
document.addEventListener('alpine:initialized', () => {
|
|
179
|
+
for (const [tpl, el] of adopted) {
|
|
180
|
+
if (tpl._x_marker && tpl._x_currentIfEl === el && el.isConnected && !el._x_marker) {
|
|
181
|
+
window.Alpine.initTree(el);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}, { once: true });
|
|
143
185
|
}
|
|
144
186
|
if (typeof document !== 'undefined') {
|
|
145
187
|
document.addEventListener('alpine:init', reconcilePrerenderClones, { once: true });
|
|
@@ -236,7 +278,6 @@
|
|
|
236
278
|
'tabs',
|
|
237
279
|
'text-edit',
|
|
238
280
|
'slides',
|
|
239
|
-
'resize',
|
|
240
281
|
'colorpicker',
|
|
241
282
|
'datepicker',
|
|
242
283
|
'charts',
|
package/lib/manifest.markdown.js
CHANGED
|
@@ -516,9 +516,11 @@ async function initializeMarkdownPlugin() {
|
|
|
516
516
|
markdownSource = normalizeContent(newContent);
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
-
// Skip if no content
|
|
519
|
+
// Skip if no content. Baked content stays visible: a reactive
|
|
520
|
+
// expression can evaluate empty at boot (data still loading),
|
|
521
|
+
// and hiding then would blank prerendered output.
|
|
520
522
|
if (!markdownSource || markdownSource.trim() === '') {
|
|
521
|
-
element.style.opacity = '0';
|
|
523
|
+
if (!hasContent) element.style.opacity = '0';
|
|
522
524
|
return;
|
|
523
525
|
}
|
|
524
526
|
|
|
@@ -607,11 +609,13 @@ async function initializeMarkdownPlugin() {
|
|
|
607
609
|
return;
|
|
608
610
|
}
|
|
609
611
|
|
|
610
|
-
// Prerendered MPA pages with baked content are already correct
|
|
611
|
-
// the effect
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
612
|
+
// Prerendered MPA pages with baked content are already correct on
|
|
613
|
+
// FIRST paint, but the effect still has to register: a reactive
|
|
614
|
+
// expression (x-markdown="note") must keep re-rendering when its
|
|
615
|
+
// value changes. File-backed markdown never reaches here — the
|
|
616
|
+
// prerender strips x-markdown from those (data-mnfst-md-src), so
|
|
617
|
+
// registering the effect cannot re-fetch baked articles.
|
|
618
|
+
// updateContent already no-ops when the render equals the baked HTML.
|
|
615
619
|
|
|
616
620
|
// Classify the source: real expressions evaluate; bare markdown/paths
|
|
617
621
|
// are quoted so Alpine doesn't choke on them.
|