mnfst 0.5.232 → 0.5.234
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.d.ts +4 -2
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.edit.js +129 -40
- package/lib/manifest.integrity.json +6 -6
- package/lib/manifest.js +44 -3
- package/lib/manifest.rich.text.js +642 -175
- package/lib/manifest.sw.js +1 -1
- package/package.json +2 -1
package/lib/manifest.d.ts
CHANGED
|
@@ -433,11 +433,13 @@ export interface ManifestEdit {
|
|
|
433
433
|
/** Set an attribute: a string sets it, `true` sets it empty, `null`/`false` removes it. */
|
|
434
434
|
attr(name: string, value: string | boolean | null, el?: Element): boolean;
|
|
435
435
|
classes: ManifestEditClasses;
|
|
436
|
-
/** Component scope of
|
|
436
|
+
/** Component scope of `el`'s area (default `$edit.target`): `scope(el)` reads it.
|
|
437
437
|
* Instance overrides apply to an area that wraps exactly one component with no x-edit region
|
|
438
438
|
* of its own; a component among other content is a host piece of a static area instead, and
|
|
439
439
|
* a component's own region is edited in its own file. */
|
|
440
|
-
scope(
|
|
440
|
+
scope(el?: Element | null): 'instance' | 'main' | null;
|
|
441
|
+
/** Set the component scope of `el`'s area (default `$edit.target`); false if it has none. */
|
|
442
|
+
scope(value: 'instance' | 'main', el?: Element | null): boolean;
|
|
441
443
|
/** Revert a component element's instance overrides (the area element reverts all). */
|
|
442
444
|
revert(el?: Element): boolean;
|
|
443
445
|
}
|
package/lib/manifest.data.js
CHANGED
package/lib/manifest.edit.js
CHANGED
|
@@ -186,6 +186,14 @@
|
|
|
186
186
|
}
|
|
187
187
|
const sanitizeFor = (el, html) => !window.ManifestRichText ? sanitizeHTML(html)
|
|
188
188
|
: authoredRich(el) ? window.ManifestRichText.sanitize(html, true, true) : leafClean(html, el);
|
|
189
|
+
// Text written into an element. A leaf shows exactly what is logged; a rich field owns its markup:
|
|
190
|
+
// it is written only when it differs as logged, then settles (keeping its editing affordances)
|
|
191
|
+
function writeText(el, html) {
|
|
192
|
+
const safe = sanitizeFor(el, html);
|
|
193
|
+
if (el.innerHTML === safe || (authoredRich(el) && sanitizeFor(el, el.innerHTML.trim()) === safe)) return;
|
|
194
|
+
el.innerHTML = safe;
|
|
195
|
+
if (el._rt && typeof el._rt.settle === 'function') el._rt.settle();
|
|
196
|
+
}
|
|
189
197
|
|
|
190
198
|
function sanitizeHTML(html) {
|
|
191
199
|
const t = document.createElement('template'); t.innerHTML = String(html == null ? '' : html);
|
|
@@ -529,7 +537,7 @@
|
|
|
529
537
|
const base = area._baseText || {}, baseC = area._baseClass || {}, baseS = area._baseStyle || {};
|
|
530
538
|
const pick = (prop, fallback) => iv[prop] !== undefined ? iv[prop] : (mv[prop] !== undefined ? mv[prop] : fallback);
|
|
531
539
|
const text = pick('text', base[p]), cls = pick('class', baseC[p]), sty = pick('style', baseS[p]);
|
|
532
|
-
if (text !== undefined)
|
|
540
|
+
if (text !== undefined) writeText(el, text);
|
|
533
541
|
if (cls !== undefined) writeClass(el, cls);
|
|
534
542
|
if (sty !== undefined && el.getAttribute('style') !== sty) { if (sty === '') el.removeAttribute('style'); else el.setAttribute('style', sty); }
|
|
535
543
|
});
|
|
@@ -540,7 +548,7 @@
|
|
|
540
548
|
if (path == null) return false;
|
|
541
549
|
if (prop === 'text') value = sanitizeFor(el, value);
|
|
542
550
|
if (before === undefined) before = prop === 'text' ? el._preEdit : prop === 'style' ? el._preStyle : el._preClass;
|
|
543
|
-
if (before === value) return false;
|
|
551
|
+
if (before === value || (prop === 'text' && before != null && sanitizeFor(el, before) === value)) return false;
|
|
544
552
|
const sig = nodeSig(el);
|
|
545
553
|
pushDelta(scope === 'main' ? { kind: 'cmp-main', component, path, prop, value, before, sig, region } : { kind: 'cmp-inst', component, region, path, prop, value, before, sig });
|
|
546
554
|
applyComponentState();
|
|
@@ -640,7 +648,7 @@
|
|
|
640
648
|
|
|
641
649
|
function applyStaticProp(el, prop, v) {
|
|
642
650
|
if (v === undefined) return;
|
|
643
|
-
if (prop === 'text')
|
|
651
|
+
if (prop === 'text') return writeText(el, v);
|
|
644
652
|
if (attrRefusal(prop, v, el.tagName)) return;
|
|
645
653
|
if (prop === 'class') return writeClass(el, v);
|
|
646
654
|
if (prop === 'style') return writeStyle(el, v);
|
|
@@ -862,8 +870,9 @@
|
|
|
862
870
|
const pre = prop === 'text' ? el._preEdit : prop === 'class' ? el._preClass : prop === 'style' ? el._preStyle : undefined;
|
|
863
871
|
before = pre !== undefined ? pre : fallback;
|
|
864
872
|
}
|
|
865
|
-
if (before === value) return false;
|
|
866
|
-
|
|
873
|
+
if (before === value || (prop === 'text' && before != null && sanitizeFor(el, before) === value)) return false;
|
|
874
|
+
// A rich field owns its markup: the logged value is never written back into it
|
|
875
|
+
if (prop === 'text' && document.activeElement !== el && !authoredRich(el)) writeText(el, value);
|
|
867
876
|
const last = log[cursor - 1];
|
|
868
877
|
if (opts.coalesce && last && cursor === log.length && last.kind === 'st-node' && last.region === region && last.path === path && last.prop === prop && Date.now() - (stamps.get(last) || 0) < COALESCE_MS) {
|
|
869
878
|
if (last.before === value) { log.pop(); cursor--; } else { last.value = value; stamps.set(last, Date.now()); }
|
|
@@ -901,7 +910,7 @@
|
|
|
901
910
|
value = String(value);
|
|
902
911
|
if (el && el._editRich) value = sanitizeFor(el, value);
|
|
903
912
|
const before = el ? el._preEdit : undefined;
|
|
904
|
-
if (before === value) return false;
|
|
913
|
+
if (before === value || (el && el._editRich && before != null && sanitizeFor(el, before) === value)) return false;
|
|
905
914
|
pushDelta({ kind: 'data-val', region: key(area), source, id, field, value, before });
|
|
906
915
|
applyDataValues();
|
|
907
916
|
return true;
|
|
@@ -1163,7 +1172,8 @@
|
|
|
1163
1172
|
if (kind === 'component') return false;
|
|
1164
1173
|
const block = asPiece(el);
|
|
1165
1174
|
if (!block) return false;
|
|
1166
|
-
if (action === 'copy'
|
|
1175
|
+
if (action === 'copy') return !isHostEl(block);
|
|
1176
|
+
if (action === 'cut') return canDo('copy', block) && canDo('remove', block); // a cut is a copy and a remove
|
|
1167
1177
|
if (action === 'move') return movable(block);
|
|
1168
1178
|
const lockAs = action === 'duplicate' ? 'duplicate' : 'remove';
|
|
1169
1179
|
return capOf(block.parentElement, 'move') && !lockedFor(block, lockAs);
|
|
@@ -1408,6 +1418,8 @@
|
|
|
1408
1418
|
const region = s.crossOK ? dropArea(x, y, s.area) : s.area;
|
|
1409
1419
|
if (!region) return null;
|
|
1410
1420
|
const hit = document.elementFromPoint ? document.elementFromPoint(x, y) : null;
|
|
1421
|
+
// Over the stand-in itself: it stays where it is
|
|
1422
|
+
if (hit && s.ghost && (hit === s.ghost || s.ghost.contains(hit))) return null;
|
|
1411
1423
|
if (hit && hit !== region && region.contains(hit)) {
|
|
1412
1424
|
// Inside a component host: the host is what is hit (its markup is another file's)
|
|
1413
1425
|
let from = hit;
|
|
@@ -1417,6 +1429,9 @@
|
|
|
1417
1429
|
const parent = el.parentElement;
|
|
1418
1430
|
if (!isContainer(parent)) continue;
|
|
1419
1431
|
if (isHostEl(el)) { if (accepts(parent) && fits(s.item, parent) && !s.item.contains(parent)) return beside(el, x, y, parent); continue; }
|
|
1432
|
+
// Over the container the piece came from, or one holding the stand-in (a gap, its padding):
|
|
1433
|
+
// it stays in, placed by reading order; it leaves only when the pointer does
|
|
1434
|
+
if (s.ghost && (el === s.home || el.contains(s.ghost)) && accepts(el) && fits(s.item, el)) return { container: el, ref: afterElement(el, x, y) };
|
|
1420
1435
|
if (isContainer(el) && accepts(el) && fits(s.item, el) && inMiddle(el, x, y, parent)) {
|
|
1421
1436
|
if (s.ghost && s.ghost.parentElement !== el) s.nested = { el, rect: el.getBoundingClientRect(), horiz: flowAxis(parent) === 'x' };
|
|
1422
1437
|
return { container: el, ref: afterElement(el, x, y) };
|
|
@@ -1429,9 +1444,12 @@
|
|
|
1429
1444
|
}
|
|
1430
1445
|
// Reading-order insertion: the first sibling past the pointer.
|
|
1431
1446
|
function afterElement(c, x, y) {
|
|
1447
|
+
const column = flowAxis(c) === 'y';
|
|
1432
1448
|
for (const el of sortableChildren(c)) {
|
|
1433
1449
|
if (el === dragged || el.hasAttribute('data-edit-dragging') || lockedFor(el, 'move')) continue;
|
|
1434
1450
|
const b = el.getBoundingClientRect(), cx = b.left + b.width / 2, cy = b.top + b.height / 2;
|
|
1451
|
+
// Stacked rows: past a row once the pointer is past its middle (wherever it is across)
|
|
1452
|
+
if (column && !/inline/.test(getComputedStyle(el).display)) { if (cy > y) return el; continue; }
|
|
1435
1453
|
const sameRow = Math.abs(cy - y) < b.height / 2;
|
|
1436
1454
|
if (cy - y > b.height / 2 || (sameRow && cx > x)) return el;
|
|
1437
1455
|
}
|
|
@@ -2041,7 +2059,7 @@
|
|
|
2041
2059
|
area.querySelectorAll('[data-rich-text]:not([data-rich-text-bound])').forEach(el => {
|
|
2042
2060
|
if (el._editRt || !capOf(el, 'text') || !/^[@~]/.test(el.getAttribute('data-edit-key') || '')) return;
|
|
2043
2061
|
if (el._richBound) return; el._richBound = true;
|
|
2044
|
-
el.addEventListener('focusin', () => { el._preEdit = el
|
|
2062
|
+
el.addEventListener('focusin', () => { el._preEdit = leafText(el); });
|
|
2045
2063
|
el.addEventListener('blur', () => {
|
|
2046
2064
|
const a = regionOf(el) || area; // the region the element sits in NOW, not where it was armed
|
|
2047
2065
|
const commit = classify(a) === 'component' ? commitComponentNode : commitStaticNode;
|
|
@@ -2298,12 +2316,25 @@
|
|
|
2298
2316
|
el._cssvar.persist = !!(modifiers && modifiers.includes('persist'));
|
|
2299
2317
|
cssvarControls.add(el);
|
|
2300
2318
|
}
|
|
2319
|
+
// A colour input takes #rrggbb only: any CSS colour (a theme's oklch(), var() resolved) is read off a canvas pixel
|
|
2320
|
+
let colorCanvas = null;
|
|
2321
|
+
function hexColor(v) {
|
|
2322
|
+
if (/^#[0-9a-f]{6}$/i.test(v)) return v.toLowerCase();
|
|
2323
|
+
try {
|
|
2324
|
+
colorCanvas = colorCanvas || document.createElement('canvas');
|
|
2325
|
+
const g = colorCanvas.getContext && colorCanvas.getContext('2d', { willReadFrequently: true });
|
|
2326
|
+
if (!g) return null;
|
|
2327
|
+
const probe = document.createElement('i'); probe.style.color = v; if (!probe.style.color) return null;
|
|
2328
|
+
g.clearRect(0, 0, 1, 1); g.fillStyle = '#000'; g.fillStyle = v; g.fillRect(0, 0, 1, 1);
|
|
2329
|
+
return '#' + [...g.getImageData(0, 0, 1, 1).data.slice(0, 3)].map(x => x.toString(16).padStart(2, '0')).join('');
|
|
2330
|
+
} catch { return null; }
|
|
2331
|
+
}
|
|
2301
2332
|
function syncCssVarInput(el) {
|
|
2302
2333
|
const cv = el._cssvar; if (!cv || !('value' in el)) return;
|
|
2303
2334
|
const cur = getComputedStyle(themeTargetEl(cv.scope)).getPropertyValue(cv.var).trim();
|
|
2304
2335
|
if (!cur) return;
|
|
2305
|
-
const next = el.dataset.unit ? String(parseFloat(cur)) : cur;
|
|
2306
|
-
if (el.value !== next) el.value = next;
|
|
2336
|
+
const next = el.type === 'color' ? hexColor(cur) : el.dataset.unit ? String(parseFloat(cur)) : cur;
|
|
2337
|
+
if (next != null && el.value !== next) el.value = next;
|
|
2307
2338
|
}
|
|
2308
2339
|
function armCssVar(el) {
|
|
2309
2340
|
const cv = el._cssvar; if (!cv || !cv.var) return;
|
|
@@ -2332,10 +2363,24 @@
|
|
|
2332
2363
|
});
|
|
2333
2364
|
cssvarControls.forEach(syncCssVarInput);
|
|
2334
2365
|
}
|
|
2335
|
-
function rebaseTheme() {
|
|
2336
|
-
Object.keys(themeBaseline).forEach(kk => { const sep = kk.indexOf('|'); themeBaseline[kk] = themeTargetEl(kk.slice(0, sep) || null).style.getPropertyValue(kk.slice(sep + 1)); });
|
|
2337
|
-
themeTouched.
|
|
2338
|
-
}
|
|
2366
|
+
function rebaseTheme(keep) {
|
|
2367
|
+
Object.keys(themeBaseline).forEach(kk => { if (keep && keep.has('theme|' + kk)) return; const sep = kk.indexOf('|'); themeBaseline[kk] = themeTargetEl(kk.slice(0, sep) || null).style.getPropertyValue(kk.slice(sep + 1)); });
|
|
2368
|
+
[...themeTouched].forEach(kk => { if (!(keep && keep.has('theme|' + kk))) themeTouched.delete(kk); });
|
|
2369
|
+
}
|
|
2370
|
+
// A scoped value lives in its scope element's inline style, and is written there, never to a
|
|
2371
|
+
// stylesheet: with its x-edit area's tree when the element is a static piece of one ('area'),
|
|
2372
|
+
// else found by its own x-edit.theme name ('own'). Inside generated content it can't be (null).
|
|
2373
|
+
function inlineScope(scope) {
|
|
2374
|
+
const el = scope && themeScopes[scope]; if (!el || generatedBy(el)) return null;
|
|
2375
|
+
const a = regionOf(el), k = el.getAttribute('data-edit-key');
|
|
2376
|
+
if (a && classify(a) === 'static' && !sharedKey(a) && k && /^[@~]/.test(k)) return 'area';
|
|
2377
|
+
if (a && a !== el) return null;
|
|
2378
|
+
const named = areaByKey(scope);
|
|
2379
|
+
return !named || named === el ? 'own' : null;
|
|
2380
|
+
}
|
|
2381
|
+
const inlineScoped = () => new Set(Object.values(themeState()).filter(d => d.scope && inlineScope(d.scope) === 'area').map(d => themeScopes[d.scope]));
|
|
2382
|
+
// The component file a scope element is authored in, if any.
|
|
2383
|
+
const scopeComponent = (el) => { for (let n = el; n; n = n.parentElement) if (n._manifestComponent) return n._manifestComponent; return null; };
|
|
2339
2384
|
// A custom-property value must stay inside its declaration (same tokenizer as the
|
|
2340
2385
|
// source writer): strings and url(...) are opaque; script schemes in url() and
|
|
2341
2386
|
// expression() are refused anywhere; data: urls only as images.
|
|
@@ -2383,12 +2428,16 @@
|
|
|
2383
2428
|
const cur = themeState()[kk]; if (cur && cur.value === value) return; // dedupe
|
|
2384
2429
|
pushDelta({ kind: 'theme', scope: scope || null, var: v, value }, !!persist);
|
|
2385
2430
|
}
|
|
2386
|
-
//
|
|
2431
|
+
// Global vars → the page's theme stylesheet (the server's choice); scoped ones inline
|
|
2387
2432
|
function themePatches() {
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2433
|
+
const st = Object.values(themeState()), out = [];
|
|
2434
|
+
st.filter(d => !inlineScope(d.scope)).forEach(d => out.push({ kind: 'theme', scope: d.scope || null, var: d.var, value: d.value, file: null }));
|
|
2435
|
+
// A scope element of its own: its style attribute, as the page has it now
|
|
2436
|
+
new Set(st.filter(d => d.scope && inlineScope(d.scope) === 'own').map(d => d.scope)).forEach(scope => {
|
|
2437
|
+
const el = themeScopes[scope], comp = scopeComponent(el), tree = { key: '', edits: { style: ownStyle(el) || null } };
|
|
2438
|
+
out.push(comp ? { kind: 'static-tree', region: scope, component: comp, tree } : { kind: 'static-tree', region: scope, tree });
|
|
2391
2439
|
});
|
|
2440
|
+
return out;
|
|
2392
2441
|
}
|
|
2393
2442
|
|
|
2394
2443
|
|
|
@@ -2448,8 +2497,21 @@
|
|
|
2448
2497
|
};
|
|
2449
2498
|
walk(area, '');
|
|
2450
2499
|
}
|
|
2451
|
-
const warnedRendered = new Set(), renderedRegions = new Set();
|
|
2500
|
+
const warnedRendered = new Set(), renderedRegions = new Set(), unwritableWhy = new Map();
|
|
2501
|
+
// A list whose records mnfst-run can't write: inline x-data, or a source manifest.json does not declare as a file.
|
|
2502
|
+
function dataUnwritable(area) {
|
|
2503
|
+
if (classify(area) !== 'data') return null;
|
|
2504
|
+
const m = (dataSourceExpr(area) || '').match(/\$x\.([\w$]+)/);
|
|
2505
|
+
if (!m) return 'inline x-data list: mnfst-run writes data only to CSV/JSON/YAML sources declared in manifest.json';
|
|
2506
|
+
const mf = window.ManifestComponentsRegistry && window.ManifestComponentsRegistry.manifest;
|
|
2507
|
+
if (!mf) return null;
|
|
2508
|
+
const src = (mf.data || {})[m[1]];
|
|
2509
|
+
if (src == null) return `data source '${m[1]}' is not declared in manifest.json: mnfst-run writes data only to CSV/JSON/YAML files declared there`;
|
|
2510
|
+
return typeof src === 'string' ? null : `data source '${m[1]}' is not a file (a cloud or remote source): save it with $x.${m[1]}.$update, not mnfst-run`;
|
|
2511
|
+
}
|
|
2452
2512
|
function armArea(area) {
|
|
2513
|
+
const dw = dataUnwritable(area);
|
|
2514
|
+
if (dw) { renderedRegions.add(key(area)); unwritableWhy.set(key(area), dw); if (!warnedRendered.has(key(area))) { warnedRendered.add(key(area)); console.warn(`[edit] x-edit="${key(area)}": ${dw}; its edits stay on the page (and reach onPublish)`); } }
|
|
2453
2515
|
if (area._edit.rendered) renderedRegions.add(key(area));
|
|
2454
2516
|
if (area._edit.rendered && !warnedRendered.has(key(area))) { warnedRendered.add(key(area)); console.warn(`[edit] x-edit="${key(area)}" is inside content ${area._edit.rendered} renders: it edits on the page (and reaches onPublish), but mnfst-run cannot write it to a source file`); }
|
|
2455
2517
|
uneditIn(null);
|
|
@@ -2545,7 +2607,7 @@
|
|
|
2545
2607
|
return Object.keys(e).length ? e : null;
|
|
2546
2608
|
};
|
|
2547
2609
|
const nontrivial = (n) => n.edits || n.children || n.key[0] === '~';
|
|
2548
|
-
const live = keyMap(staticAreas());
|
|
2610
|
+
const live = keyMap(staticAreas()), scoped = inlineScoped();
|
|
2549
2611
|
staticAreas().forEach(area => {
|
|
2550
2612
|
const region = key(area), comp = sourceComponent(area);
|
|
2551
2613
|
const ids = new Proxy({}, { get: (_, k) => { const o = originOf(String(k)), a = areaByKey(decodeURIComponent(o.slice(1, o.indexOf('@', 1)))); const m = baseIds && a && baseIds[sourceFile(a)]; return m ? m[o] : undefined; } });
|
|
@@ -2565,6 +2627,7 @@
|
|
|
2565
2627
|
const at = originAt.get(originOf(id)); if (at != null) n.at = at;
|
|
2566
2628
|
if (ids && ids[originOf(id)]) n.fp = ids[originOf(id)];
|
|
2567
2629
|
const e = editsFor(id); if (e) n.edits = e;
|
|
2630
|
+
if (scoped.has(el)) n.edits = Object.assign({}, n.edits, { style: ownStyle(el) || null });
|
|
2568
2631
|
if (!(e && 'text' in e) && !isRendered(el) && !el.hasAttribute('data-rich-text')) {
|
|
2569
2632
|
const kids = treeKids(el).map(c => build(c)).filter(Boolean);
|
|
2570
2633
|
const structural = ss.touched.has(isRoot ? '#' + region : id);
|
|
@@ -2606,7 +2669,8 @@
|
|
|
2606
2669
|
return e;
|
|
2607
2670
|
};
|
|
2608
2671
|
const dv = dataValueState();
|
|
2609
|
-
|
|
2672
|
+
const dvRegion = {}; log.slice(0, cursor).forEach(d => { if (d.kind === 'data-val') dvRegion[d.source] = d.region; });
|
|
2673
|
+
Object.entries(dv).forEach(([source, recs]) => Object.entries(recs).forEach(([id, fields]) => Object.entries(fields).forEach(([field, value]) => patches.push({ kind: 'data-val', region: dvRegion[source], source, id, field, value }))));
|
|
2610
2674
|
const { main, inst, reverts } = componentState();
|
|
2611
2675
|
Object.entries(main).forEach(([component, paths]) => { const edits = toEdits(paths); if (edits.length) patches.push({ kind: 'component', scope: 'main', component, edits }); });
|
|
2612
2676
|
new Set([...Object.keys(inst), ...Object.keys(reverts)]).forEach(region => {
|
|
@@ -2623,7 +2687,7 @@
|
|
|
2623
2687
|
// `keep` (areas a publish left out), whose identities are left as they are.
|
|
2624
2688
|
function rebase(keep) {
|
|
2625
2689
|
const mounted = (r) => r == null || (!!areaByKey(r) && !(keep && keep.has(r)));
|
|
2626
|
-
const pending = (d) => (d.kind === 'st-move' ? [d.from, d.to] : [d.region]).some(r => !mounted(r));
|
|
2690
|
+
const pending = (d) => d.kind === 'theme' ? !!keep && (keep.has('theme|' + (d.scope || '') + '|' + d.var) || keep.has(d.scope)) : (d.kind === 'st-move' ? [d.from, d.to] : [d.region]).some(r => !mounted(r));
|
|
2627
2691
|
// Runtime locks are app state, not source: carried over by element.
|
|
2628
2692
|
const locks = [];
|
|
2629
2693
|
const lockState = lockStateNow();
|
|
@@ -2657,7 +2721,7 @@
|
|
|
2657
2721
|
if (kind === 'component' && a._armed) armComponent(a);
|
|
2658
2722
|
lastSnap[key(a)] = snapshot(a, kind);
|
|
2659
2723
|
});
|
|
2660
|
-
rebaseTheme();
|
|
2724
|
+
rebaseTheme(keep);
|
|
2661
2725
|
relock();
|
|
2662
2726
|
saveState(); refresh();
|
|
2663
2727
|
}
|
|
@@ -2783,7 +2847,7 @@
|
|
|
2783
2847
|
// fingerprints of keys with twins there (which may not be the elements this page shows).
|
|
2784
2848
|
let overNewer = false;
|
|
2785
2849
|
function publishNow(opts) {
|
|
2786
|
-
const done = (r, keep) => {
|
|
2850
|
+
const done = (r, keep, heldRes) => {
|
|
2787
2851
|
if (r && r.base && baseFiles) Object.entries(r.base).forEach(([f, h]) => { if (!overNewer || !/\.html?$/i.test(f)) baseFiles[f] = h; });
|
|
2788
2852
|
if (r && r.ids && baseIds) {
|
|
2789
2853
|
if (!overNewer) baseIds = r.ids;
|
|
@@ -2792,7 +2856,7 @@
|
|
|
2792
2856
|
const stale = r && Array.isArray(r.stale) ? r.stale : [];
|
|
2793
2857
|
if (estore) estore.stale = stale;
|
|
2794
2858
|
if (stale.length) document.dispatchEvent(new CustomEvent('edit:stale', { detail: { files: stale } }));
|
|
2795
|
-
if (published(keep ? { results: r.results.filter(x => !(x && keep.has(x.region))) } : r)) rebase(keep);
|
|
2859
|
+
if (published(keep ? { results: r.results.filter(x => !(x && (keep.has(x.region) || (heldRes && heldRes.has(x))))) } : r)) rebase(keep);
|
|
2796
2860
|
takeHeldReload();
|
|
2797
2861
|
return r;
|
|
2798
2862
|
};
|
|
@@ -2801,14 +2865,19 @@
|
|
|
2801
2865
|
const leftOut = (r, patches) => {
|
|
2802
2866
|
if (!r || !Array.isArray(r.results) || (Array.isArray(r.stale) && r.stale.length)) return null;
|
|
2803
2867
|
const failed = new Set(r.results.filter(x => x && x.failed != null).map(x => x.failed));
|
|
2804
|
-
|
|
2868
|
+
const own = new Set(patches.filter((p, i) => r.results[i] && r.results[i].status === 'error' && r.results[i].failed == null && p.kind !== 'static-tree'));
|
|
2869
|
+
if ((!failed.size && !own.size) || r.results.some((x, i) => x && x.status === 'error' && !failed.has(patches[i] && patches[i].region) && !own.has(patches[i]))) return null;
|
|
2805
2870
|
tie(failed);
|
|
2806
|
-
|
|
2871
|
+
const out = (p) => own.has(p) || (p.region != null && failed.has(p.region));
|
|
2872
|
+
if (!patches.some(p => !out(p))) return null;
|
|
2873
|
+
return Object.assign(failed, { own, out });
|
|
2807
2874
|
};
|
|
2875
|
+
// What a held-back patch keeps in the log: its region's edits, or its theme value.
|
|
2876
|
+
const keepOf = (p) => p.kind === 'theme' ? 'theme|' + (p.scope || '') + '|' + p.var : p.region;
|
|
2808
2877
|
// An area inside generated content (x-markdown output and the like) edits on the page,
|
|
2809
2878
|
// but its markup is in no file mnfst-run can write: it is refused by name, not sent.
|
|
2810
2879
|
const renderedBy = (region) => { const a = areaByKey(region); return a && a._edit.rendered; };
|
|
2811
|
-
const unwritable = (p) =>
|
|
2880
|
+
const unwritable = (p) => p.region != null && p.kind !== 'theme' && renderedRegions.has(p.region);
|
|
2812
2881
|
const send = () => {
|
|
2813
2882
|
commitFocused(true); // typing under way publishes with the rest (identities are re-derived from the page after)
|
|
2814
2883
|
const patches = buildPatches();
|
|
@@ -2816,24 +2885,25 @@
|
|
|
2816
2885
|
if (!patches.length) { takeHeldReload(); return Promise.resolve({ results: [] }); } // nothing to send: a held reload goes now
|
|
2817
2886
|
const post = (ps) => { inFlight++; return fetch('/__edit/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ patches: ps, base: baseFiles }) }).then(r => r.json()).catch(err => { settled(null); throw err; }); };
|
|
2818
2887
|
const off = tie(new Set(patches.filter(unwritable).map(p => p.region)));
|
|
2819
|
-
const offResult = (p) => ({ region: p.region, status: 'error', reason:
|
|
2820
|
-
? `x-edit="${p.region}" is inside content ${renderedBy(p.region)} renders, so mnfst-run can't write it to a source file`
|
|
2888
|
+
const offResult = (p) => ({ region: p.region, status: 'error', reason: unwritableWhy.has(p.region) && unwritable(p) ? `x-edit="${p.region}": ${unwritableWhy.get(p.region)}`
|
|
2889
|
+
: renderedBy(p.region) && unwritable(p) ? `x-edit="${p.region}" is inside content ${renderedBy(p.region)} renders, so mnfst-run can't write it to a source file`
|
|
2821
2890
|
: `not published: tied by a move to x-edit="${off.via.get(p.region)}", which mnfst-run can't write` });
|
|
2822
2891
|
const sendable = patches.filter(p => !off.has(p.region));
|
|
2823
2892
|
// The sent part's answer, merged with what was held back, as one result per patch.
|
|
2824
2893
|
const finish = (r, held, sent) => {
|
|
2825
|
-
const keep = new Set([...off, ...(held || [])]);
|
|
2826
|
-
const
|
|
2827
|
-
const
|
|
2894
|
+
const keep = new Set([...off, ...(held || []), ...(held ? sent.filter(held.out).map(keepOf) : [])]);
|
|
2895
|
+
const heldRes = new Set();
|
|
2896
|
+
const results = patches.map(p => { if (off.has(p.region)) { const x = offResult(p); heldRes.add(x); return x; } const x = r.results[sent.indexOf(p)]; if (held && held.out(p)) heldRes.add(x); return x; });
|
|
2897
|
+
const o = done(Object.assign({}, r, { results }), keep.size ? keep : undefined, heldRes); settled(r, !!(opts && opts.rebase)); return o;
|
|
2828
2898
|
};
|
|
2829
2899
|
if (!sendable.length) { const o = done({ results: patches.map(offResult) }, off); return Promise.resolve(o); }
|
|
2830
2900
|
return post(sendable).then(r => {
|
|
2831
2901
|
const out = leftOut(r, sendable);
|
|
2832
2902
|
if (!out) return finish(r, null, sendable);
|
|
2833
2903
|
settled(r, false);
|
|
2834
|
-
const rest = sendable.filter(p => !out.
|
|
2904
|
+
const rest = sendable.filter(p => !out.out(p));
|
|
2835
2905
|
return post(rest).then(r2 => {
|
|
2836
|
-
const results = sendable.map((p, i) => out.
|
|
2906
|
+
const results = sendable.map((p, i) => out.out(p)
|
|
2837
2907
|
? (r.results[i] && r.results[i].status === 'error' ? r.results[i] : { region: p.region, status: 'error', reason: `not published: tied by a move to x-edit="${out.via.get(p.region) || [...out].find(x => !out.via.has(x))}", which failed` })
|
|
2838
2908
|
: r2.results[rest.indexOf(p)]);
|
|
2839
2909
|
return finish(Object.assign({}, r2, { results }), out, sendable);
|
|
@@ -2874,7 +2944,8 @@
|
|
|
2874
2944
|
ancestor opts it out. Text that is still being typed keeps the browser's own
|
|
2875
2945
|
undo until it commits on blur; fields and x-rich-text keep theirs always. A leaf
|
|
2876
2946
|
on Rich Text uses Rich Text's history while it has steps (richHistoryKey). */
|
|
2877
|
-
|
|
2947
|
+
// Text as it would be logged: every baseline and comparison goes through the same sanitizer
|
|
2948
|
+
const leafText = (el) => el._editField && !el._editRich ? el.textContent : sanitizeFor(el, el.innerHTML.trim());
|
|
2878
2949
|
const hotkeysOff = (el) => !!el.closest('[data-edit-hotkeys="off"]');
|
|
2879
2950
|
// A focused leaf's baseline follows history rewrites under the caret.
|
|
2880
2951
|
function rebaseFocused() {
|
|
@@ -2935,8 +3006,10 @@
|
|
|
2935
3006
|
refresh();
|
|
2936
3007
|
}
|
|
2937
3008
|
function waitForData(area, cb) { const expr = dataSourceExpr(area), tpl = listTpl(area); let n = 0; const t = setInterval(() => { let r = false; try { r = Array.isArray(window.Alpine.evaluate(tpl, expr)); } catch { } if (r) { clearInterval(t); cb(); } else if (++n > 100) clearInterval(t); }, 50); }
|
|
3009
|
+
let inited = false;
|
|
2938
3010
|
function init() {
|
|
2939
|
-
if (!window.Alpine || !Alpine.directive) return;
|
|
3011
|
+
if (inited || !window.Alpine || !Alpine.directive) return;
|
|
3012
|
+
inited = true;
|
|
2940
3013
|
// Alpine walks only x-data trees: a page with none still gets its areas
|
|
2941
3014
|
if (document.body && !document.body.hasAttribute('x-data')) document.body.setAttribute('x-data', '{}');
|
|
2942
3015
|
Alpine.directive('edit', (el, { modifiers, expression }, { cleanup }) => {
|
|
@@ -2968,14 +3041,20 @@
|
|
|
2968
3041
|
block(node) { return pieceOf(node); },
|
|
2969
3042
|
attr(name, value, el) { return setAttr(name, value, el); },
|
|
2970
3043
|
classes: classesApi,
|
|
2971
|
-
scope(value, el) { void this.version;
|
|
3044
|
+
scope(value, el) { void this.version; if (value && value.nodeType === 1) return scopeOf(value); return value == null ? scopeOf(el) : setScope(value, el); },
|
|
2972
3045
|
revert(el) { return revert(el); },
|
|
2973
3046
|
clearConflicts() { conflicts = []; this.conflicts = []; saveState(); }
|
|
2974
3047
|
});
|
|
2975
3048
|
estore = Alpine.store('edit');
|
|
2976
|
-
|
|
3049
|
+
const editMagic = () => Alpine.store('edit');
|
|
3050
|
+
Alpine.magic('edit', editMagic);
|
|
3051
|
+
// The loader's stand-in answered $edit until now: it hands over, and effects that read it run again
|
|
3052
|
+
window.__manifestEditMagic = editMagic;
|
|
3053
|
+
const kept = window.__manifestEditStubWrites; // writes made on the stand-in ($edit.onPublish = …)
|
|
3054
|
+
if (kept) Object.keys(kept).forEach(k => { try { estore[k] = kept[k]; } catch { } delete kept[k]; });
|
|
3055
|
+
if (window.__manifestEditStub) setTimeout(() => { window.__manifestEditStub.v++; }, 0);
|
|
2977
3056
|
loadState();
|
|
2978
|
-
loadBase();
|
|
3057
|
+
if (window.__mnfstEdit) loadBase(); // only mnfst-run --edit serves the base
|
|
2979
3058
|
setTimeout(() => { armAll(); restore(); }, 450);
|
|
2980
3059
|
document.addEventListener('keydown', onHistoryKey);
|
|
2981
3060
|
window.addEventListener('click', onAreaLinkClick, true);
|
|
@@ -2984,6 +3063,16 @@
|
|
|
2984
3063
|
document.addEventListener('dropdown:context', onContextCapture, true);
|
|
2985
3064
|
// SPA navigation: the state so far is saved under the page it was made on.
|
|
2986
3065
|
window.addEventListener('manifest:route-change', () => { if (lsPath !== location.pathname) { lsPath = location.pathname; saveState(); } });
|
|
3066
|
+
adopt();
|
|
3067
|
+
}
|
|
3068
|
+
// Loaded after Alpine started: x-edit elements its walk passed get their attributes re-set
|
|
3069
|
+
// (Alpine runs them as new directives); those it never walked (no x-data above) are walked now.
|
|
3070
|
+
function adopt() {
|
|
3071
|
+
if (!window.__manifestAlpineStarted && ![...document.querySelectorAll('[x-data]')].some(e => e._x_marker)) return;
|
|
3072
|
+
const isEdit = (n) => /^x-edit(\.|$)/.test(n);
|
|
3073
|
+
const found = [...document.querySelectorAll('*')].filter(el => !el._edit && [...el.attributes].some(a => isEdit(a.name)));
|
|
3074
|
+
found.forEach(el => { if (el._x_marker) [...el.attributes].filter(a => isEdit(a.name)).forEach(a => el.setAttribute(a.name, a.value)); });
|
|
3075
|
+
if (found.some(el => !el._x_marker)) Alpine.initTree(document.body);
|
|
2987
3076
|
}
|
|
2988
3077
|
document.addEventListener('alpine:init', init);
|
|
2989
3078
|
if (window.Alpine && Alpine.directive) setTimeout(init, 0);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-dsIWnH0qCAGSzAXMGrd8fvOrVl4tOIE6j5tC35JPTrQXZ56qLD36ARyvhR7vi3Id",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-pj/2ZCUMXz3zTWPZyICJHfCPnz5srho9URrw0Sl0nfWuvh5KqvoT/g3Kg4uyv7yl",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
|
|
@@ -11,24 +11,24 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-JalISaBBWNFl3ggx92Hb3B52EpwwG3gzgra6Q+js0OgZ8vrFKMD48CuBox2N9A5T",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384
|
|
14
|
+
"manifest.data.js": "sha384-+wRqEd8FFPqaSX5gZhb66OgkAHTiSQDFK6W1agFMceKtd5nVL7OLh6p5gC96AJ3Z",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
16
|
"manifest.defer.js": "sha384-pMRGqQKiEoJhpUcD40QeM4jEHau7sz9Xn/YCpiUamT1T4xJBZDSiYX+xnVrSWoq+",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
18
18
|
"manifest.dropdowns.js": "sha384-acufUps7R9WjqB4XXW5JLACqniuHJGM4CNTb2rF4B2Q/jv7ZRmsAwfr2McanOz3s",
|
|
19
|
-
"manifest.edit.js": "sha384-
|
|
19
|
+
"manifest.edit.js": "sha384-XUtJ+EXXYh+5afgYuVpXurjvylSP/o72Y4mQR5wxdkIFQ/NDFBVvRFAfbPXMInZc",
|
|
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
23
|
"manifest.markdown.js": "sha384-heyDR40LXHaAmAuA+fa381uFwShk1oAqpFFY6E2Qq4T3VN0TQfRqyc++MF40K5Hj",
|
|
24
24
|
"manifest.native.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
25
25
|
"manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
|
|
26
|
-
"manifest.rich.text.js": "sha384-
|
|
26
|
+
"manifest.rich.text.js": "sha384-yJNb+EIumFIJx2KiKmLojwyHLCKwIn+48noxh3gUuZ10YZOvJJoanzAU7NLIBF1s",
|
|
27
27
|
"manifest.router.js": "sha384-QkKhRaCbdXEOxhj+ojT4jeLUCtQepBnqP+QWTIWdb6kUWD+mVzPN8hf6jhgschpW",
|
|
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-alPjmWaugQV/aCb6l7hVITwZ6Z5k47SJ9jHXmNUN093iUu8ikbVggwLSGNb84Ib1",
|
|
32
32
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
34
|
"manifest.toasts.js": "sha384-3zkWxzw693pxg3QDFsoYCA3CqYaLKmBaMArULXODD66Utolpewl0OAeTB92aWUVv",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
|
|
37
37
|
"manifest.utilities.js": "sha384-L5uXMLFBIF6LUM1haRjd4eGjHSWuqsCYSpPZRPYosNfJsMC5U5TnmjBxIhrZRe2d",
|
|
38
38
|
"manifest.virtual.js": "sha384-ZDNoF9QVhVcVpkjIK6NH15S6I5c6OBshiJpXSOYjNsIB+298I0a7g3aFO+jyq4Vb",
|
|
39
|
-
"manifest.js": "sha384-
|
|
39
|
+
"manifest.js": "sha384-m5WiqHv+23ZnBvvIt9NtUGE2Gupaw9jDpKuonIH9HZCHc+LR8wbitDImJoNPH9CI"
|
|
40
40
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -597,14 +597,55 @@
|
|
|
597
597
|
const A = window.Alpine;
|
|
598
598
|
if (window.ManifestRichText || !A || !A.magic || !A.reactive || window.__manifestRichStub) return;
|
|
599
599
|
const state = window.__manifestRichStub = A.reactive({ v: 0 });
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
600
|
+
// Writes ($rich.value = …, $rich.all = …) are kept per element and applied to its field at handover
|
|
601
|
+
const writes = window.__manifestRichStubWrites = [], perEl = new WeakMap();
|
|
602
|
+
const deref = (r) => r && typeof r.deref === 'function' ? r.deref() : r;
|
|
603
|
+
const base = { value: '', link: '', selection: null, all: {}, active: () => false, can: () => false, markdown: () => '', html: () => '' };
|
|
604
|
+
const idleFor = (el) => {
|
|
605
|
+
if (!perEl.has(el)) perEl.set(el, new Proxy(base, {
|
|
606
|
+
get: (t, k) => { const w = writes.find(x => deref(x[0]) === el && x[1] === k); return w ? w[2] : k in t ? t[k] : (typeof k === 'string' ? () => { } : undefined); },
|
|
607
|
+
// The latest write per element and key; elements gone from the page are let go
|
|
608
|
+
set: (t, k, v) => {
|
|
609
|
+
for (let i = writes.length - 1; i >= 0; i--) { const e = deref(writes[i][0]); if (!e || !e.isConnected || (e === el && writes[i][1] === k)) writes.splice(i, 1); }
|
|
610
|
+
writes.push([typeof WeakRef === 'function' ? new WeakRef(el) : el, k, v]);
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
}));
|
|
614
|
+
return perEl.get(el);
|
|
615
|
+
};
|
|
616
|
+
A.magic('rich', (el, u) => { void state.v; return window.__manifestRichMagic ? window.__manifestRichMagic(el, u) : idleFor(el); });
|
|
603
617
|
};
|
|
604
618
|
document.addEventListener('alpine:init', install, { once: true });
|
|
605
619
|
if (window.Alpine && window.Alpine.magic) install();
|
|
606
620
|
}
|
|
607
621
|
stubRichMagic();
|
|
622
|
+
// $edit likewise: Edit loads behind a gate (data-plugins or Manifest.loadPlugin('edit')), so until
|
|
623
|
+
// it registers, $edit is an idle stand-in; Edit bumps `v` and the stand-in delegates to it.
|
|
624
|
+
let editStubArmed = false;
|
|
625
|
+
function stubEditMagic() {
|
|
626
|
+
if (typeof window === 'undefined' || typeof document === 'undefined' || editStubArmed) return;
|
|
627
|
+
editStubArmed = true;
|
|
628
|
+
const install = () => {
|
|
629
|
+
const A = window.Alpine;
|
|
630
|
+
if (window.__manifestEditMagic || !A || !A.magic || !A.reactive || window.__manifestEditStub) return;
|
|
631
|
+
const state = window.__manifestEditStub = A.reactive({ v: 0 });
|
|
632
|
+
const none = () => false;
|
|
633
|
+
// Writes ($edit.onPublish = …) are kept and applied to the store at handover
|
|
634
|
+
const writes = window.__manifestEditStubWrites = {};
|
|
635
|
+
const idle = new Proxy({
|
|
636
|
+
active: false, canUndo: false, canRedo: false, version: 0, stale: [], conflicts: [], onPublish: null, target: null,
|
|
637
|
+
can: none, scope: () => null, block: () => null, patches: () => [], export: () => ({ log: [], cursor: 0 }),
|
|
638
|
+
publish: () => Promise.resolve({ results: [] }), undo: () => Promise.resolve(false), redo: () => Promise.resolve(false),
|
|
639
|
+
classes: new Proxy({}, { get: () => none })
|
|
640
|
+
}, { get: (t, k) => k in writes ? writes[k] : k in t ? t[k] : (typeof k === 'string' ? none : undefined), set: (t, k, v) => { writes[k] = v; return true; } });
|
|
641
|
+
A.magic('edit', (el, u) => { void state.v; return window.__manifestEditMagic ? window.__manifestEditMagic(el, u) : idle; });
|
|
642
|
+
};
|
|
643
|
+
document.addEventListener('alpine:init', install, { once: true });
|
|
644
|
+
if (window.Alpine && window.Alpine.magic) install();
|
|
645
|
+
}
|
|
646
|
+
stubEditMagic();
|
|
647
|
+
// Plugins loaded after Alpine started adopt what its first walk already passed
|
|
648
|
+
if (typeof document !== 'undefined') document.addEventListener('alpine:initialized', () => { window.__manifestAlpineStarted = true; }, { once: true });
|
|
608
649
|
// Framework web components — <x-*> tags that are NOT project components.
|
|
609
650
|
const FRAMEWORK_TAG_RE = /^x-(code|code-group)$/;
|
|
610
651
|
function detectUsagePlugins(alreadyLoaded, root = typeof document === 'undefined' ? null : document) {
|