@panphora/clayjs 0.2.1 → 0.4.1
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/README.md +103 -7
- package/clay-data.js +1 -1
- package/clay-internals.js +19 -0
- package/clay.js +19 -3
- package/package.json +5 -2
- package/sap.js +5 -5
- package/src/attrs/onaftersave.js +12 -1
- package/src/attrs/refetch-on-save.js +4 -4
- package/src/attrs/save-freeze.js +47 -19
- package/src/core/admin-contenteditable.js +2 -2
- package/src/core/admin-inputs.js +2 -2
- package/src/core/admin-onclick.js +2 -2
- package/src/core/admin-resources.js +23 -14
- package/src/core/autosave.js +11 -1
- package/src/core/edit-mode.js +2 -2
- package/src/core/host-attrs.js +46 -0
- package/src/core/is-edit-mode.js +14 -6
- package/src/core/persist.js +12 -2
- package/src/core/save-core.js +199 -290
- package/src/core/save.js +121 -82
- package/src/core/snapshot.js +89 -36
- package/src/core/unsaved-warning.js +8 -2
- package/src/dom/all.js +4 -29
- package/src/dom/form-data.js +1 -1
- package/src/dom/nearest.js +11 -9
- package/src/internals/index.js +69 -0
- package/src/lib/cache-bust.js +10 -0
- package/src/lib/cookie.js +50 -15
- package/src/lib/extension-noise.js +5 -1
- package/src/lib/mutation.js +65 -18
- package/src/lib/region-policy.js +17 -2
- package/src/lib/root-attrs.js +45 -0
- package/src/lib/throttle.js +25 -13
- package/src/loader.js +18 -24
- package/src/options/options.js +1 -3
- package/src/plugins/demo.js +7 -1
- package/src/plugins/undo.js +3 -3
- package/src/sync/live-sync.js +77 -76
- package/src/ui/dialogs.js +10 -1
- package/src/ui/index.js +0 -10
- package/src/utils/debounce.js +24 -7
- package/src/vendor/control-serialize.vendor.js +2 -1
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +29 -16
- package/src/vendor/richclay.vendor.js +29 -22
package/src/options/options.js
CHANGED
|
@@ -240,9 +240,7 @@ const optionVisibility = {
|
|
|
240
240
|
}
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
window.hyperclay = window.hyperclay || {};
|
|
245
|
-
window.hyperclay.optionVisibility = optionVisibility;
|
|
243
|
+
window.clay.optionVisibility = optionVisibility;
|
|
246
244
|
|
|
247
245
|
export default optionVisibility;
|
|
248
246
|
|
package/src/plugins/demo.js
CHANGED
|
@@ -20,12 +20,18 @@
|
|
|
20
20
|
// returning visitors' old saved bodies morph over the new page.
|
|
21
21
|
|
|
22
22
|
import { morph } from "../vendor/hyper-morph.vendor.js";
|
|
23
|
+
import { STRIP_FROM_SAVE } from "../lib/region-policy.js";
|
|
23
24
|
|
|
24
25
|
const KEY = "clay:demo:" +
|
|
25
26
|
(document.documentElement.getAttribute("demo-key") || window.location.pathname);
|
|
26
27
|
const LATENCY_MS = 800;
|
|
28
|
+
// What the demo's fake server must not persist. The region half comes from
|
|
29
|
+
// region-policy's STRIP_FROM_SAVE rather than being re-listed here: this used to
|
|
30
|
+
// name "[save-remove]" alone and so missed the canonical clay="no-save" spelling,
|
|
31
|
+
// which meant the demo saved the very regions no-save promises it never will. The
|
|
32
|
+
// rest is editor chrome the real save lane strips by other means.
|
|
27
33
|
const CHROME_SELECTOR = [
|
|
28
|
-
|
|
34
|
+
STRIP_FROM_SAVE,
|
|
29
35
|
"[data-richclay-toolbar]",
|
|
30
36
|
"[data-richclay-menu]",
|
|
31
37
|
"[data-richclay-dialog]",
|
package/src/plugins/undo.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Re-exports the vendored hyper-undo singleton and auto-starts it in edit mode
|
|
2
|
-
// so apps "just work". The loader wires it onto clay.undo
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// so apps "just work". The loader wires it onto clay.undo; the vendor's own
|
|
3
|
+
// window auto-export is suppressed by the bootstrap's __hyperclayNoAutoExport
|
|
4
|
+
// flag.
|
|
5
5
|
|
|
6
6
|
import { undo } from '../vendor/hyper-undo.vendor.js'
|
|
7
7
|
import { isEditMode } from '../core/is-edit-mode.js'
|
package/src/sync/live-sync.js
CHANGED
|
@@ -40,6 +40,8 @@ import Mutation from "../lib/mutation.js";
|
|
|
40
40
|
import { isSnapshotRemoved } from "../lib/region-policy.js";
|
|
41
41
|
import { isEditMode } from "../core/is-edit-mode.js";
|
|
42
42
|
import { mergeTagRecognizers } from "./merge-tags.js";
|
|
43
|
+
import { serializeForSync } from '../core/snapshot.js';
|
|
44
|
+
import { isTabLocalRootAttr } from '../lib/root-attrs.js';
|
|
43
45
|
|
|
44
46
|
class LiveSync {
|
|
45
47
|
constructor() {
|
|
@@ -58,6 +60,8 @@ class LiveSync {
|
|
|
58
60
|
|
|
59
61
|
this.debounceMs = 150;
|
|
60
62
|
this.debounceTimer = null;
|
|
63
|
+
this._sendInFlight = false;
|
|
64
|
+
this._queuedSend = null;
|
|
61
65
|
this.isPaused = false;
|
|
62
66
|
this.isDestroyed = false;
|
|
63
67
|
this.debug = false;
|
|
@@ -96,7 +100,7 @@ class LiveSync {
|
|
|
96
100
|
// against the live elements so that the next save can produce the same
|
|
97
101
|
// identityMap, and afterNodeMorphed transfers IDs from incoming parsed
|
|
98
102
|
// elements onto the live elements they morphed into.
|
|
99
|
-
this.idCounter =
|
|
103
|
+
this.idCounter = 0;
|
|
100
104
|
this.liveWeakMap = new WeakMap();
|
|
101
105
|
|
|
102
106
|
// Callbacks
|
|
@@ -118,28 +122,15 @@ class LiveSync {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
125
|
+
* Mint the per-tab sender identity used for echo suppression. Lives on the
|
|
126
|
+
* instance, never in storage: sessionStorage is COPIED into a duplicated tab,
|
|
127
|
+
* so a persisted id made the duplicate treat the original's edits as its own
|
|
128
|
+
* echoes and the two tabs silently stopped syncing. Instance lifetime is the
|
|
129
|
+
* durability that matters, since EventSource auto-reconnect reuses this same
|
|
130
|
+
* instance.
|
|
123
131
|
*/
|
|
124
132
|
generateClientId() {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
id = sessionStorage.getItem('livesync-client-id');
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// sessionStorage might not be available
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!id) {
|
|
134
|
-
id = Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
|
|
135
|
-
try {
|
|
136
|
-
sessionStorage.setItem('livesync-client-id', id);
|
|
137
|
-
} catch (e) {
|
|
138
|
-
// That's okay
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return id;
|
|
133
|
+
return Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
|
|
143
134
|
}
|
|
144
135
|
|
|
145
136
|
/**
|
|
@@ -208,6 +199,7 @@ class LiveSync {
|
|
|
208
199
|
}
|
|
209
200
|
|
|
210
201
|
clearTimeout(this.debounceTimer);
|
|
202
|
+
this._queuedSend = null;
|
|
211
203
|
|
|
212
204
|
// Cancel any pending frame and clear the queue. A morph already in
|
|
213
205
|
// flight cannot be aborted; the isDestroyed check in _runPending guards
|
|
@@ -221,27 +213,8 @@ class LiveSync {
|
|
|
221
213
|
this._pendingIdentityMap = null;
|
|
222
214
|
}
|
|
223
215
|
|
|
224
|
-
_loadIdCounter() {
|
|
225
|
-
try {
|
|
226
|
-
const raw = sessionStorage.getItem('livesync-id-counter');
|
|
227
|
-
const n = parseInt(raw || '0', 10);
|
|
228
|
-
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
229
|
-
} catch (e) {
|
|
230
|
-
return 0;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
_persistIdCounter() {
|
|
235
|
-
try {
|
|
236
|
-
sessionStorage.setItem('livesync-id-counter', String(this.idCounter));
|
|
237
|
-
} catch (e) {
|
|
238
|
-
// Storage full / sandboxed — fall back to in-memory only.
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
216
|
_mintId() {
|
|
243
217
|
this.idCounter++;
|
|
244
|
-
this._persistIdCounter();
|
|
245
218
|
return `${this.clientId}:${this.idCounter}`;
|
|
246
219
|
}
|
|
247
220
|
|
|
@@ -387,8 +360,10 @@ class LiveSync {
|
|
|
387
360
|
if (this.isDestroyed) return;
|
|
388
361
|
|
|
389
362
|
const pageUrl = encodeURIComponent(window.location.href);
|
|
390
|
-
const
|
|
391
|
-
|
|
363
|
+
const path = `/_/live-sync/stream?page-url=${pageUrl}&lane=${this.lane}&resume-id=${this.resumeId}`;
|
|
364
|
+
// Resolved against the real origin: a <base href> in the authored document
|
|
365
|
+
// would otherwise point the sync stream at an origin the document chose.
|
|
366
|
+
this.sse = new EventSource(new URL(path, window.location.origin).href);
|
|
392
367
|
|
|
393
368
|
this.sse.onopen = () => {
|
|
394
369
|
console.log('[LiveSync] Connected');
|
|
@@ -482,7 +457,7 @@ class LiveSync {
|
|
|
482
457
|
// after dispatchEvent returns, so reading outerHTML and walking children
|
|
483
458
|
// must happen now.
|
|
484
459
|
this._log('snapshot-ready received, preparing to send');
|
|
485
|
-
const html = clone
|
|
460
|
+
const html = serializeForSync(clone);
|
|
486
461
|
const identityMap = this._buildIdentityMap(document.documentElement, clone);
|
|
487
462
|
this.sendUpdate(html, identityMap);
|
|
488
463
|
};
|
|
@@ -491,42 +466,67 @@ class LiveSync {
|
|
|
491
466
|
}
|
|
492
467
|
|
|
493
468
|
/**
|
|
494
|
-
* Send full HTML to the server (debounced)
|
|
495
|
-
*
|
|
469
|
+
* Send full HTML to the server (debounced, single-flight).
|
|
470
|
+
*
|
|
471
|
+
* One POST on the wire at a time, with at most one newer payload queued; a
|
|
472
|
+
* fresher snapshot replaces the queued one, because peers only ever need the
|
|
473
|
+
* latest state. Two concurrent fetches could reach the server in either order,
|
|
474
|
+
* and last-write-wins then stored the OLDER snapshot for every peer.
|
|
496
475
|
*/
|
|
497
476
|
sendUpdate(html, identityMap) {
|
|
498
477
|
clearTimeout(this.debounceTimer);
|
|
499
478
|
|
|
500
479
|
this.debounceTimer = setTimeout(() => {
|
|
501
|
-
|
|
502
|
-
if (html === this.lastHtml) {
|
|
503
|
-
this._log('Skipping send - HTML unchanged');
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
|
|
508
|
-
|
|
509
|
-
fetch('/_/live-sync/save', {
|
|
510
|
-
method: 'POST',
|
|
511
|
-
headers: { 'Content-Type': 'application/json', 'Page-URL': window.location.href },
|
|
512
|
-
body: JSON.stringify({
|
|
513
|
-
html: html,
|
|
514
|
-
sender: this.clientId,
|
|
515
|
-
identityMap: identityMap
|
|
516
|
-
})
|
|
517
|
-
}).then(response => {
|
|
518
|
-
if (response.ok) {
|
|
519
|
-
this.lastHtml = html;
|
|
520
|
-
} else {
|
|
521
|
-
console.warn('[LiveSync] Save returned status:', response.status);
|
|
522
|
-
}
|
|
523
|
-
}).catch(err => {
|
|
524
|
-
console.error('[LiveSync] Save failed:', err);
|
|
525
|
-
if (this.onError) this.onError(err);
|
|
526
|
-
});
|
|
480
|
+
this._enqueueSend(html, identityMap);
|
|
527
481
|
}, this.debounceMs);
|
|
528
482
|
}
|
|
529
483
|
|
|
484
|
+
_enqueueSend(html, identityMap) {
|
|
485
|
+
if (this._sendInFlight) {
|
|
486
|
+
this._queuedSend = { html, identityMap };
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
this._postUpdate(html, identityMap);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
_postUpdate(html, identityMap) {
|
|
493
|
+
// Skip if unchanged
|
|
494
|
+
if (html === this.lastHtml) {
|
|
495
|
+
this._log('Skipping send - HTML unchanged');
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
|
|
500
|
+
|
|
501
|
+
this._sendInFlight = true;
|
|
502
|
+
|
|
503
|
+
// Absolute against the real origin, so a <base href> in the page cannot
|
|
504
|
+
// redirect the whole document to an origin the author picked.
|
|
505
|
+
fetch(new URL('/_/live-sync/save', window.location.origin).href, {
|
|
506
|
+
method: 'POST',
|
|
507
|
+
headers: { 'Content-Type': 'application/json', 'Page-URL': window.location.href },
|
|
508
|
+
body: JSON.stringify({
|
|
509
|
+
html: html,
|
|
510
|
+
sender: this.clientId,
|
|
511
|
+
identityMap: identityMap
|
|
512
|
+
})
|
|
513
|
+
}).then(response => {
|
|
514
|
+
if (response.ok) {
|
|
515
|
+
this.lastHtml = html;
|
|
516
|
+
} else {
|
|
517
|
+
console.warn('[LiveSync] Save returned status:', response.status);
|
|
518
|
+
}
|
|
519
|
+
}).catch(err => {
|
|
520
|
+
console.error('[LiveSync] Save failed:', err);
|
|
521
|
+
if (this.onError) this.onError(err);
|
|
522
|
+
}).finally(() => {
|
|
523
|
+
this._sendInFlight = false;
|
|
524
|
+
const queued = this._queuedSend;
|
|
525
|
+
this._queuedSend = null;
|
|
526
|
+
if (queued) this._postUpdate(queued.html, queued.identityMap);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
530
|
/**
|
|
531
531
|
* Apply an update received from the server. Morphs the entire document.
|
|
532
532
|
*
|
|
@@ -660,6 +660,11 @@ class LiveSync {
|
|
|
660
660
|
const id = parsedWeakMap.get(newEl);
|
|
661
661
|
if (id) liveWeakMap.set(oldEl, id);
|
|
662
662
|
};
|
|
663
|
+
// A peer may neither write these onto our root nor, by not carrying them,
|
|
664
|
+
// take ours away. Returning false is hyper-morph's veto for both directions
|
|
665
|
+
// (it calls this for updateType 'update' and 'remove' alike).
|
|
666
|
+
const beforeAttributeUpdated = (name, element) =>
|
|
667
|
+
isTabLocalRootAttr(name, element) ? false : undefined;
|
|
663
668
|
|
|
664
669
|
try {
|
|
665
670
|
// Morph entire document. We MUST await — HyperMorph.morph returns a
|
|
@@ -683,7 +688,7 @@ class LiveSync {
|
|
|
683
688
|
mergeTags: mergeTagRecognizers
|
|
684
689
|
},
|
|
685
690
|
key,
|
|
686
|
-
callbacks: { afterNodeMorphed }
|
|
691
|
+
callbacks: { afterNodeMorphed, beforeAttributeUpdated }
|
|
687
692
|
});
|
|
688
693
|
|
|
689
694
|
// Restore viewport. Done after morph so layout has settled.
|
|
@@ -716,10 +721,6 @@ class LiveSync {
|
|
|
716
721
|
document.dispatchEvent(new CustomEvent('clay:sync-applied', {
|
|
717
722
|
detail: { seq }
|
|
718
723
|
}));
|
|
719
|
-
// vendor-compat: hypercms's form panel listens for the legacy name.
|
|
720
|
-
document.dispatchEvent(new CustomEvent('hyperclay:livesync-applied', {
|
|
721
|
-
detail: { seq }
|
|
722
|
-
}));
|
|
723
724
|
} finally {
|
|
724
725
|
this._log('applyUpdate - morph complete, resuming mutations');
|
|
725
726
|
Mutation.resume();
|
package/src/ui/dialogs.js
CHANGED
|
@@ -6,9 +6,18 @@ import copyToClipboard from "../utils/copy-to-clipboard.js";
|
|
|
6
6
|
const CLOSE_BUTTON_SVG = `<svg viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"><path class="micromodal__close-bg" d="M132 132.5 1 1.5h131v131Z" /><path class="micromodal__close-x" fill-rule="evenodd" clip-rule="evenodd" d="M0 0h3v1.5h1.5V3H6v1.5h1.5V6H9v1.5h1.5V9H12v1.5h1.5V12H15v1.5h1.5V15H18v1.5h1.5V18H21v1.5h1.5V21H24v1.5h1.5V24H27v1.5h1.5V27H30v1.5h1.5V30H33v1.5h1.5V33H36v1.5h1.5V36H39v1.5h1.5V39H42v1.5h1.5V42H45v1.5h1.5V45H48v1.5h1.5V48H51v1.5h1.5V51H54v1.5h1.5V54H57v1.5h1.5V57H60v1.5h1.5V60H63v1.5h1.5V63H66v1.5h1.5V66H69v1.5h1.5V69H72v1.5h1.5V72H75v1.5h1.5V75H78v1.5h1.5V78H81v1.5h1.5V81H84v1.5h1.5V84H87v1.5h1.5V87H90v1.5h1.5V90H93v1.5h1.5V93H96v1.5h1.5V96H99v1.5h1.5V99h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v3h-3V132H129v-1.5h-1.5V129H126v-1.5h-1.5V126H123v-1.5h-1.5V123H120v-1.5h-1.5V120H117v-1.5h-1.5V117H114v-1.5h-1.5V114H111v-1.5h-1.5V111H108v-1.5h-1.5V108H105v-1.5h-1.5V105H102v-1.5h-1.5V102H99v-1.5h-1.5V99H96v-1.5h-1.5V96H93v-1.5h-1.5V93H90v-1.5h-1.5V90H87v-1.5h-1.5V87H84v-1.5h-1.5V84H81v-1.5h-1.5V81H78v-1.5h-1.5V78H75v-1.5h-1.5V75H72v-1.5h-1.5V72H69v-1.5h-1.5V69H66v-1.5h-1.5V66H63v-1.5h-1.5V63H60v-1.5h-1.5V60H57v-1.5h-1.5V57H54v-1.5h-1.5V54H51v-1.5h-1.5V51H48v-1.5h-1.5V48H45v-1.5h-1.5V45H42v-1.5h-1.5V42H39v-1.5h-1.5V39H36v-1.5h-1.5V36H33v-1.5h-1.5V33H30v-1.5h-1.5V30H27v-1.5h-1.5V27H24v-1.5h-1.5V24H21v-1.5h-1.5V21H18v-1.5h-1.5V18H15v-1.5h-1.5V15H12v-1.5h-1.5V12H9v-1.5H7.5V9H6V7.5H4.5V6H3V4.5H1.5V3H0V0ZM108.8 22h5.2v5.1h-2.6v2.6H109v2.6h-2.6v2.6h-2.6v2.5h-2.6v5.2h2.6V45h2.6v2.6h2.6v2.6h2.5v2.6h2.6V58h-5.1v-2.6h-2.6V53h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6h-5.2v2.6H91v2.6h-2.6v2.6h-2.6v2.5h-2.6V58H78v-5.1h2.6v-2.6H83v-2.6h2.6v-2.6h2.6v-2.5h2.6v-5.2h-2.6V35h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6H78V22h5.2v2.6h2.5V27h2.6v2.6h2.6v2.6h2.5v2.6h5.2v-2.6h2.5v-2.6h2.6v-2.6h2.6v-2.5h2.5V22Z" /></svg>`;
|
|
7
7
|
const CONFIRM_BUTTON_SVG = `<div style="width: 28px;"><svg viewBox="0 0 60 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M34.5 0.75H43.5V5.25H49V9.75H54.5V14.25H60V18.75H54.5V23.25H49V27.75H43.5V32.25H34.5V27.75H40V23.25H45.5V18.75H0V14.25H45.5V9.75H40V5.25H34.5V0.75Z" fill="white"/></svg></div>`;
|
|
8
8
|
|
|
9
|
+
// Only for the prompt's default value. promptText and extraContent are markup by
|
|
10
|
+
// design, and callers pass elements through them.
|
|
11
|
+
function escapeAttribute(value) {
|
|
12
|
+
return String(value)
|
|
13
|
+
.replaceAll("&", "&")
|
|
14
|
+
.replaceAll('"', """)
|
|
15
|
+
.replaceAll("<", "<");
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
function createModal(promptText, yesCallback, extraContent = "", includeInput = false, defaultValue = "") {
|
|
10
19
|
const inputHtml = includeInput
|
|
11
|
-
? `<div><input class="micromodal__input" type="text" value="${defaultValue}" required></div>`
|
|
20
|
+
? `<div><input class="micromodal__input" type="text" value="${escapeAttribute(defaultValue)}" required></div>`
|
|
12
21
|
: "";
|
|
13
22
|
|
|
14
23
|
themodal.html = `<div>
|
package/src/ui/index.js
CHANGED
|
@@ -18,16 +18,6 @@ clay.modal = themodal;
|
|
|
18
18
|
if (typeof window.toast === "undefined") window.toast = toast;
|
|
19
19
|
if (typeof window.toastPersistent === "undefined") window.toastPersistent = toastPersistent;
|
|
20
20
|
|
|
21
|
-
// vendor-compat mirror: sap reads window.hyperclay.consent (the legacy name for
|
|
22
|
-
// clay.confirm). Never CREATE window.hyperclay here: sap's mutation bridge treats
|
|
23
|
-
// any truthy window.hyperclay as "a hub is coming" and waits forever for it. Core
|
|
24
|
-
// creates the object (with Mutation) and fires clay:mutation-ready; attach then.
|
|
25
|
-
function mirrorConsent() {
|
|
26
|
-
if (window.hyperclay && !window.hyperclay.consent) window.hyperclay.consent = consent;
|
|
27
|
-
}
|
|
28
|
-
if (window.hyperclay) mirrorConsent();
|
|
29
|
-
else document.addEventListener("clay:mutation-ready", mirrorConsent, { once: true });
|
|
30
|
-
|
|
31
21
|
// Automatic save feedback. clay:save-saving stays deliberately silent (a toast for
|
|
32
22
|
// a sub-second transient is noise). Keep your own toast lib? The events are public.
|
|
33
23
|
document.addEventListener("clay:save-saved", (e) =>
|
package/src/utils/debounce.js
CHANGED
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
// debounce.js
|
|
2
2
|
function debounce(callback, delay) {
|
|
3
3
|
let timeoutId;
|
|
4
|
-
let
|
|
4
|
+
let pending = [];
|
|
5
5
|
|
|
6
6
|
return function (...args) {
|
|
7
7
|
const ctx = this;
|
|
8
8
|
clearTimeout(timeoutId);
|
|
9
9
|
|
|
10
|
-
return new Promise((resolve) => {
|
|
11
|
-
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
pending.push({ resolve, reject });
|
|
12
12
|
|
|
13
13
|
timeoutId = setTimeout(() => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.then(value => { for (const r of resolvers) r(value); });
|
|
14
|
+
const waiting = pending;
|
|
15
|
+
pending = [];
|
|
16
|
+
settle(callback, ctx, args, waiting);
|
|
18
17
|
}, delay);
|
|
19
18
|
});
|
|
20
19
|
};
|
|
21
20
|
}
|
|
22
21
|
|
|
22
|
+
// Settle every caller piggybacking on this window, from both branches. The
|
|
23
|
+
// callback is invoked synchronously so timing is unchanged; only the failure
|
|
24
|
+
// path is new. Without it a throw escapes into the timer, where no caller is
|
|
25
|
+
// listening, and everyone waiting on that window waits forever.
|
|
26
|
+
function settle(callback, ctx, args, waiting) {
|
|
27
|
+
let result;
|
|
28
|
+
try {
|
|
29
|
+
result = callback.apply(ctx, args);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
for (const p of waiting) p.reject(error);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
Promise.resolve(result).then(
|
|
35
|
+
value => { for (const p of waiting) p.resolve(value); },
|
|
36
|
+
error => { for (const p of waiting) p.reject(error); },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
export default debounce;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// GENERATED — do not edit. Vendored from sapjs/src/control-serialize.js
|
|
2
|
-
// via sapjs `npm run
|
|
2
|
+
// via sapjs `npm run propagate`, which writes every client's copy from one
|
|
3
|
+
// table. Edit the sapjs source and re-run.
|
|
3
4
|
|
|
4
5
|
// The one place that knows how a form control's live state maps to the DOM
|
|
5
6
|
// attributes that survive serialization (cloneNode / outerHTML).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var hyperundo=(()=>{var T=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of te(t))!re.call(e,o)&&o!==n&&T(e,o,{get:()=>t[o],enumerable:!(r=ee(t,o))||r.enumerable});return e};var ae=e=>oe(T({},"__esModule",{value:!0}),e);var me={};ne(me,{default:()=>pe,undo:()=>C});function ie(e,t=null){let n=[];if(e.type==="attributes"){let r=e.target,o=e.attributeName,i=e.oldValue,u=r.getAttribute(o);return i==null&&u!=null?n.push({kind:"attr-add",target:r,name:o,newValue:u}):i!=null&&u==null?n.push({kind:"attr-remove",target:r,name:o,oldValue:i}):i!==u&&n.push({kind:"attr-set",target:r,name:o,oldValue:i,newValue:u}),n}if(e.type==="characterData")return n.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),n;if(e.type==="childList"){let r=e.target,o=e.nextSibling,i=t?Array.from(e.addedNodes).filter(d=>!t(d)):Array.from(e.addedNodes),u=t?Array.from(e.removedNodes).filter(d=>!t(d)):Array.from(e.removedNodes);return i.length>0&&n.push({kind:"add",parent:r,nodes:i,before:o}),u.length>0&&n.push({kind:"remove",parent:r,nodes:u,before:o}),n}return n}function P(e,t=null){let n=[],r=new Map;for(let o of e){if(o.type==="attributes"){let i=o.target,u=o.attributeName,d=r.get(i);d||(d=new Map,r.set(i,d)),d.has(u)||(d.set(u,{idx:n.length,oldValue:o.oldValue}),n.push(null));continue}for(let i of ie(o,t))n.push(i)}for(let[o,i]of r)for(let[u,d]of i){let l=d.oldValue,m=o.getAttribute(u),p=null;l===m?p=null:l==null?p={kind:"attr-add",target:o,name:u,newValue:m}:m==null?p={kind:"attr-remove",target:o,name:u,oldValue:l}:p={kind:"attr-set",target:o,name:u,oldValue:l,newValue:m},n[d.idx]=p}return n.filter(o=>o!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return;case"remove":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return;case"remove":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return}}var se=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],ue=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=se.join(","),U=new RegExp("^(?:"+ue.join("|")+")(?:-|$)");var de=["mutations-ignore","save-remove","save-ignore","save-freeze","no-undo","no-watch"];function le(){return typeof window<"u"&&window.hyperclay&&window.hyperclay.region&&typeof window.hyperclay.region.resolveRegionPolicy=="function"?window.hyperclay.region.resolveRegionPolicy:null}function ce(e){let t=le();if(t)try{return!t(e).undoable}catch{}let n=e&&e.nodeType!==1?e.parentElement:e;if(n&&n.closest&&n.closest(M))return!0;for(;n&&n.nodeType===1;){for(let r of de)if(n.hasAttribute&&n.hasAttribute(r))return!0;n=n.parentElement}return!1}function R(e,t,n){if(ce(e))return!0;if(n&&n.type==="attributes"){if(n.attributeName&&U.test(n.attributeName.toLowerCase()))return!0;if(t)try{if(t(n.attributeName,n.target))return!0}catch{}}return!1}function X(){let e=new Map;function t(o,i){let u=e.get(o);return u||(u=new Set,e.set(o,u)),u.add(i),()=>n(o,i)}function n(o,i){let u=e.get(o);u&&u.delete(i)}function r(o,i){let u=e.get(o);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:t,off:n,emit:r}}var fe={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function N(e){let t={...fe,...e};if(!t.scope)throw new Error("hyper-undo: scope is required");let n=X(),r=[],o=[],i=[],u=null,d=null,l=0,m=!1;function p(...s){t.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=W(g),d.observe(t.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),p("started"))}function W(s){let c=typeof document<"u"&&t.scope===document.body,f=typeof window<"u"&&window.hyperclay&&window.hyperclay.Mutation||null;return c&&f&&typeof f.createObserver=="function"?(p("sourcing records from window.hyperclay.Mutation (shared observer)"),f.createObserver(s)):new MutationObserver(s)}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),y(),r.length=0,o.length=0,i=[],l=0,p("stopped"))}function V(){let s=t.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function g(s){if(l>0)return;let c=_(s);if(c.length>0){for(let f of c)i.push(f);I()}}function _(s){let c=s.filter(f=>!R(f.target,t.ignoreAttribute,f));return P(c,f=>R(f,t.ignoreAttribute))}function I(){y(),u=setTimeout(K,t.idleWindowMs)}function y(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],v(t.idleLabel,s)}function v(s,c){for(r.push({label:s,timestamp:Date.now(),primitives:c}),o.length=0;r.length>t.maxHistory;)r.shift();p("commit",s,"primitives:",c.length),n.emit("commit")}function j(s,c){if(!m)throw new Error("hyper-undo: scope not started");g(d.takeRecords()),w();let f=c();if(f&&typeof f.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(g(d.takeRecords()),i.length===0){n.emit("commit");return}y();let b=i;i=[],v(s,b)}function G(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(y(),v(s,c))}function Z(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function $(s,{prop:c="value",oldValue:f,newValue:b}={}){!s||f===b||O({kind:"value",target:s,prop:c,oldValue:f,newValue:b})}function w(){l===0&&d&&g(d.takeRecords()),i.length!==0&&(y(),K())}function k(){l===0&&d&&g(d.takeRecords()),l++,p("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),p("resumed",l))}function J(){if(V()){E();return}w();let s=r.pop();if(s){k();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(f){p("undo primitive failed (continuing)",f)}}finally{x()}o.push(s),p("undo",s.label),n.emit("undo")}}function Q(){if(V()){E();return}w();let s=o.pop();if(s){k();try{for(let c of s.primitives)try{L(c)}catch(f){p("redo primitive failed (continuing)",f)}}finally{x()}r.push(s),p("redo",s.label),n.emit("redo")}}function Y(){d&&d.takeRecords(),r.length=0,o.length=0,i=[],y(),p("cleared"),n.emit("clear")}return{_config:t,start:H,stop:E,commit:j,commitCaptured:G,discardCaptured:Z,flush:w,record:O,recordValue:$,undo:J,redo:Q,clear:Y,pause:k,resume:x,on:n.on,off:n.off,get canUndo(){return r.length>0},get canRedo(){return o.length>0},get isPaused(){return l>0},get history(){return r.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return t.scope}}}function q(e,t){let n=r=>{let o=e.scope;if(o&&o.nodeType===1&&o.isConnected===!1){t&&t();return}let i=r.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(r.metaKey||r.ctrlKey))return;let l=(r.key||"").toLowerCase();if(l==="z"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.undo();return}if(l==="z"&&r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}if(l==="y"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",n,!0),()=>window.removeEventListener("keydown",n,!0)}var A=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],a=null,S=null,z=null,h=null;function F(e,t){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let n=q(e,t);return h={inst:e,cleanup:n},n}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var C={defaults:{shadowKeydownIn:A},start(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(S===t)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A};return a=N(n),a.start(),S=t,e.bindKeys!==!1&&(z=F(a,()=>C.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,S=null)},create(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A},r=N(n),o=null,i={_config:r._config,commit:r.commit,commitCaptured:r.commitCaptured,discardCaptured:r.discardCaptured,flush:r.flush,record:r.record,recordValue:r.recordValue,undo:r.undo,redo:r.redo,clear:r.clear,pause:r.pause,resume:r.resume,on:r.on,off:r.off,get canUndo(){return r.canUndo},get canRedo(){return r.canRedo},get isPaused(){return r.isPaused},get history(){return r.history},get scope(){return r.scope},start(){e.bindKeys&&(o=F(r,()=>i.stop())),r.start()},stop(){B(r),o=null,r.stop()}};return i},commit(e,t){return a?a.commit(e,t):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,t){return a?a.recordValue(e,t):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,t){return a?a.on(e,t):void 0},off(e,t){return a?a.off(e,t):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var pe=C;return ae(me);})();
|
|
1
|
+
var hyperundo=(()=>{var T=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of te(t))!re.call(e,o)&&o!==n&&T(e,o,{get:()=>t[o],enumerable:!(r=ee(t,o))||r.enumerable});return e};var ae=e=>oe(T({},"__esModule",{value:!0}),e);var me={};ne(me,{default:()=>pe,undo:()=>C});function ie(e,t=null){let n=[];if(e.type==="attributes"){let r=e.target,o=e.attributeName,i=e.oldValue,u=r.getAttribute(o);return i==null&&u!=null?n.push({kind:"attr-add",target:r,name:o,newValue:u}):i!=null&&u==null?n.push({kind:"attr-remove",target:r,name:o,oldValue:i}):i!==u&&n.push({kind:"attr-set",target:r,name:o,oldValue:i,newValue:u}),n}if(e.type==="characterData")return n.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),n;if(e.type==="childList"){let r=e.target,o=e.nextSibling,i=t?Array.from(e.addedNodes).filter(d=>!t(d)):Array.from(e.addedNodes),u=t?Array.from(e.removedNodes).filter(d=>!t(d)):Array.from(e.removedNodes);return i.length>0&&n.push({kind:"add",parent:r,nodes:i,before:o}),u.length>0&&n.push({kind:"remove",parent:r,nodes:u,before:o}),n}return n}function P(e,t=null){let n=[],r=new Map;for(let o of e){if(o.type==="attributes"){let i=o.target,u=o.attributeName,d=r.get(i);d||(d=new Map,r.set(i,d)),d.has(u)||(d.set(u,{idx:n.length,oldValue:o.oldValue}),n.push(null));continue}for(let i of ie(o,t))n.push(i)}for(let[o,i]of r)for(let[u,d]of i){let l=d.oldValue,m=o.getAttribute(u),p=null;l===m?p=null:l==null?p={kind:"attr-add",target:o,name:u,newValue:m}:m==null?p={kind:"attr-remove",target:o,name:u,oldValue:l}:p={kind:"attr-set",target:o,name:u,oldValue:l,newValue:m},n[d.idx]=p}return n.filter(o=>o!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return;case"remove":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return;case"remove":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return}}var se=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],ue=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=se.join(","),U=new RegExp("^(?:"+ue.join("|")+")(?:-|$)");var de=["mutations-ignore","save-remove","save-ignore","save-freeze","no-undo","no-watch"];function le(){if(typeof window>"u")return null;let e=window.clay?.region||window.hyperclay?.region;return e&&typeof e.resolveRegionPolicy=="function"?e.resolveRegionPolicy:null}function ce(e){let t=le();if(t)try{return!t(e).undoable}catch{}let n=e&&e.nodeType!==1?e.parentElement:e;if(n&&n.closest&&n.closest(M))return!0;for(;n&&n.nodeType===1;){for(let r of de)if(n.hasAttribute&&n.hasAttribute(r))return!0;n=n.parentElement}return!1}function R(e,t,n){if(ce(e))return!0;if(n&&n.type==="attributes"){if(n.attributeName&&U.test(n.attributeName.toLowerCase()))return!0;if(t)try{if(t(n.attributeName,n.target))return!0}catch{}}return!1}function X(){let e=new Map;function t(o,i){let u=e.get(o);return u||(u=new Set,e.set(o,u)),u.add(i),()=>n(o,i)}function n(o,i){let u=e.get(o);u&&u.delete(i)}function r(o,i){let u=e.get(o);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:t,off:n,emit:r}}var fe={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function N(e){let t={...fe,...e};if(!t.scope)throw new Error("hyper-undo: scope is required");let n=X(),r=[],o=[],i=[],u=null,d=null,l=0,m=!1;function p(...s){t.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=W(g),d.observe(t.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),p("started"))}function W(s){let c=typeof document<"u"&&t.scope===document.body,f=typeof window<"u"&&(window.clay?.Mutation||window.hyperclay?.Mutation)||null;return c&&f&&typeof f.createObserver=="function"?(p("sourcing records from the platform Mutation hub (shared observer)"),f.createObserver(s)):new MutationObserver(s)}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),y(),r.length=0,o.length=0,i=[],l=0,p("stopped"))}function V(){let s=t.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function g(s){if(l>0)return;let c=_(s);if(c.length>0){for(let f of c)i.push(f);I()}}function _(s){let c=s.filter(f=>!R(f.target,t.ignoreAttribute,f));return P(c,f=>R(f,t.ignoreAttribute))}function I(){y(),u=setTimeout(K,t.idleWindowMs)}function y(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],v(t.idleLabel,s)}function v(s,c){for(r.push({label:s,timestamp:Date.now(),primitives:c}),o.length=0;r.length>t.maxHistory;)r.shift();p("commit",s,"primitives:",c.length),n.emit("commit")}function j(s,c){if(!m)throw new Error("hyper-undo: scope not started");g(d.takeRecords()),w();let f=c();if(f&&typeof f.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(g(d.takeRecords()),i.length===0){n.emit("commit");return}y();let b=i;i=[],v(s,b)}function G(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(y(),v(s,c))}function Z(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function $(s,{prop:c="value",oldValue:f,newValue:b}={}){!s||f===b||O({kind:"value",target:s,prop:c,oldValue:f,newValue:b})}function w(){l===0&&d&&g(d.takeRecords()),i.length!==0&&(y(),K())}function k(){l===0&&d&&g(d.takeRecords()),l++,p("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),p("resumed",l))}function J(){if(V()){E();return}w();let s=r.pop();if(s){k();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(f){p("undo primitive failed (continuing)",f)}}finally{x()}o.push(s),p("undo",s.label),n.emit("undo")}}function Q(){if(V()){E();return}w();let s=o.pop();if(s){k();try{for(let c of s.primitives)try{L(c)}catch(f){p("redo primitive failed (continuing)",f)}}finally{x()}r.push(s),p("redo",s.label),n.emit("redo")}}function Y(){d&&d.takeRecords(),r.length=0,o.length=0,i=[],y(),p("cleared"),n.emit("clear")}return{_config:t,start:H,stop:E,commit:j,commitCaptured:G,discardCaptured:Z,flush:w,record:O,recordValue:$,undo:J,redo:Q,clear:Y,pause:k,resume:x,on:n.on,off:n.off,get canUndo(){return r.length>0},get canRedo(){return o.length>0},get isPaused(){return l>0},get history(){return r.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return t.scope}}}function q(e,t){let n=r=>{let o=e.scope;if(o&&o.nodeType===1&&o.isConnected===!1){t&&t();return}let i=r.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(r.metaKey||r.ctrlKey))return;let l=(r.key||"").toLowerCase();if(l==="z"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.undo();return}if(l==="z"&&r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}if(l==="y"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",n,!0),()=>window.removeEventListener("keydown",n,!0)}var A=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],a=null,S=null,z=null,h=null;function F(e,t){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let n=q(e,t);return h={inst:e,cleanup:n},n}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var C={defaults:{shadowKeydownIn:A},start(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(S===t)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A};return a=N(n),a.start(),S=t,e.bindKeys!==!1&&(z=F(a,()=>C.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,S=null)},create(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A},r=N(n),o=null,i={_config:r._config,commit:r.commit,commitCaptured:r.commitCaptured,discardCaptured:r.discardCaptured,flush:r.flush,record:r.record,recordValue:r.recordValue,undo:r.undo,redo:r.redo,clear:r.clear,pause:r.pause,resume:r.resume,on:r.on,off:r.off,get canUndo(){return r.canUndo},get canRedo(){return r.canRedo},get isPaused(){return r.isPaused},get history(){return r.history},get scope(){return r.scope},start(){e.bindKeys&&(o=F(r,()=>i.stop())),r.start()},stop(){B(r),o=null,r.stop()}};return i},commit(e,t){return a?a.commit(e,t):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,t){return a?a.recordValue(e,t):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,t){return a?a.on(e,t):void 0},off(e,t){return a?a.off(e,t):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var pe=C;return ae(me);})();
|
|
2
2
|
|
|
3
3
|
// Auto-export to window unless suppressed by loader.
|
|
4
4
|
if (!window.__hyperclayNoAutoExport) {
|