hyperclayjs 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/package.json +1 -1
- package/src/communication/live-sync.js +99 -125
- package/src/core/autosave.js +14 -0
- package/src/core/savePageCore.js +44 -6
- package/src/core/snapshot.js +8 -0
- package/src/core/tailwindInject.js +43 -2
- package/src/dom-utilities/insertStyleTag.js +14 -4
- package/src/hyperclay.js +35 -2
- package/src/ui/toast-hyperclay.js +14 -6
- package/src/ui/toast.js +68 -7
- package/src/utilities/mutation.js +30 -7
- package/src/vendor/hyper-morph.vendor.js +1 -1
package/README.md
CHANGED
|
@@ -57,16 +57,16 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
57
57
|
|
|
58
58
|
| Module | Size | Description |
|
|
59
59
|
|--------|------|-------------|
|
|
60
|
-
| autosave |
|
|
60
|
+
| autosave | 1.4KB | Auto-save on DOM changes |
|
|
61
61
|
| edit-mode | 1.8KB | Toggle edit mode on hyperclay on/off |
|
|
62
62
|
| edit-mode-helpers | 7.5KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
|
|
63
63
|
| option-visibility | 7.8KB | Dynamic show/hide based on ancestor state with option:attribute="value" |
|
|
64
64
|
| persist | 2.4KB | Persist input/select/textarea values to the DOM with [persist] attribute |
|
|
65
|
-
| save-core |
|
|
65
|
+
| save-core | 8.9KB | Basic save function only - hyperclay.savePage() |
|
|
66
66
|
| save-system | 12.1KB | CMD+S, [trigger-save] button, savestatus attribute |
|
|
67
67
|
| save-toast | 0.9KB | Toast notifications for save events |
|
|
68
|
-
| snapshot | 10.
|
|
69
|
-
| tailwind-inject |
|
|
68
|
+
| snapshot | 10.6KB | Source of truth for page state - captures DOM snapshots for save and sync |
|
|
69
|
+
| tailwind-inject | 1.4KB | Injects tailwind CSS link with cache-bust on save |
|
|
70
70
|
| unsaved-warning | 1.3KB | Warn before leaving page with unsaved changes |
|
|
71
71
|
|
|
72
72
|
### Custom Attributes (HTML enhancements)
|
|
@@ -86,7 +86,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
86
86
|
|--------|------|-------------|
|
|
87
87
|
| dialogs | 7.7KB | ask(), consent(), tell(), snippet() dialog functions |
|
|
88
88
|
| the-modal | 21KB | Full modal window creation system - window.theModal |
|
|
89
|
-
| toast |
|
|
89
|
+
| toast | 10.5KB | Success/error message notifications, toast(msg, msgType) |
|
|
90
90
|
|
|
91
91
|
### Utilities (Core utilities (often auto-included))
|
|
92
92
|
|
|
@@ -95,7 +95,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
95
95
|
| cache-bust | 0.6KB | Cache-bust href/src attributes |
|
|
96
96
|
| cookie | 1.4KB | Cookie management (often auto-included) |
|
|
97
97
|
| debounce | 0.4KB | Function debouncing |
|
|
98
|
-
| mutation | 13.
|
|
98
|
+
| mutation | 13.5KB | DOM mutation observation (often auto-included) |
|
|
99
99
|
| nearest | 3.4KB | Find nearest elements (often auto-included) |
|
|
100
100
|
| throttle | 0.8KB | Function throttling |
|
|
101
101
|
|
|
@@ -106,7 +106,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
106
106
|
| all-js | 14.4KB | Full DOM manipulation library |
|
|
107
107
|
| dom-ready | 0.4KB | DOM ready callback |
|
|
108
108
|
| form-data | 2KB | Extract form data as an object |
|
|
109
|
-
| style-injection |
|
|
109
|
+
| style-injection | 4.2KB | Dynamic stylesheet injection |
|
|
110
110
|
|
|
111
111
|
### String Utilities (String manipulation helpers)
|
|
112
112
|
|
|
@@ -121,28 +121,28 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
121
121
|
| Module | Size | Description |
|
|
122
122
|
|--------|------|-------------|
|
|
123
123
|
| file-upload | 10.7KB | File upload with progress |
|
|
124
|
-
| live-sync |
|
|
124
|
+
| live-sync | 11.4KB | Real-time DOM sync across browsers |
|
|
125
125
|
| send-message | 1.3KB | Message sending utility |
|
|
126
126
|
|
|
127
127
|
### Vendor Libraries (Third-party libraries)
|
|
128
128
|
|
|
129
129
|
| Module | Size | Description |
|
|
130
130
|
|--------|------|-------------|
|
|
131
|
-
| hyper-morph |
|
|
131
|
+
| hyper-morph | 17.2KB | DOM morphing with content-based element matching |
|
|
132
132
|
|
|
133
133
|
## Presets
|
|
134
134
|
|
|
135
|
-
### Minimal (~
|
|
135
|
+
### Minimal (~50.9KB)
|
|
136
136
|
Essential features for basic editing
|
|
137
137
|
|
|
138
138
|
**Modules:** `save-core`, `snapshot`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
|
|
139
139
|
|
|
140
|
-
### Standard (~
|
|
140
|
+
### Standard (~73.2KB)
|
|
141
141
|
Standard feature set for most use cases
|
|
142
142
|
|
|
143
143
|
**Modules:** `save-core`, `snapshot`, `save-system`, `unsaved-warning`, `edit-mode-helpers`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
|
|
144
144
|
|
|
145
|
-
### Everything (~
|
|
145
|
+
### Everything (~206.9KB)
|
|
146
146
|
All available features
|
|
147
147
|
|
|
148
148
|
Includes all available modules across all categories.
|
package/package.json
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* ┌─────────────────────────────────────────────────────────┐
|
|
7
7
|
* │ 1. LISTEN snapshot-ready event from save │
|
|
8
|
-
* │ (
|
|
8
|
+
* │ (full document with form values) │
|
|
9
9
|
* └─────────────────────────────────────────────────────────┘
|
|
10
10
|
* │
|
|
11
11
|
* ▼
|
|
12
12
|
* ┌─────────────────────────────────────────────────────────┐
|
|
13
|
-
* │ 2. SEND POST
|
|
13
|
+
* │ 2. SEND POST html to /live-sync/save │
|
|
14
14
|
* │ (debounced, skip if unchanged) │
|
|
15
15
|
* └─────────────────────────────────────────────────────────┘
|
|
16
16
|
* │
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* │
|
|
23
23
|
* ▼
|
|
24
24
|
* ┌─────────────────────────────────────────────────────────┐
|
|
25
|
-
* │ 4. MORPH HyperMorph
|
|
25
|
+
* │ 4. MORPH HyperMorph full documentElement │
|
|
26
26
|
* │ (preserves focus, input values) │
|
|
27
27
|
* └─────────────────────────────────────────────────────────┘
|
|
28
28
|
*
|
|
@@ -30,18 +30,19 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { HyperMorph } from "../vendor/hyper-morph.vendor.js";
|
|
33
|
+
import Mutation from "../utilities/mutation.js";
|
|
33
34
|
|
|
34
35
|
class LiveSync {
|
|
35
36
|
constructor() {
|
|
36
37
|
this.sse = null;
|
|
37
38
|
this.currentFile = null;
|
|
38
|
-
this.
|
|
39
|
-
this.lastBodyHtml = null;
|
|
39
|
+
this.lastHtml = null;
|
|
40
40
|
this.clientId = this.generateClientId();
|
|
41
41
|
this.debounceMs = 150;
|
|
42
42
|
this.debounceTimer = null;
|
|
43
43
|
this.isPaused = false;
|
|
44
44
|
this.isDestroyed = false;
|
|
45
|
+
this.debug = false;
|
|
45
46
|
|
|
46
47
|
// Store handler reference for cleanup
|
|
47
48
|
this._snapshotHandler = null;
|
|
@@ -51,24 +52,36 @@ class LiveSync {
|
|
|
51
52
|
this.onDisconnect = null;
|
|
52
53
|
this.onUpdate = null;
|
|
53
54
|
this.onError = null;
|
|
55
|
+
this.onNotification = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_log(message, data = null) {
|
|
59
|
+
if (!this.debug) return;
|
|
60
|
+
const prefix = `[LiveSync ${new Date().toISOString()}]`;
|
|
61
|
+
if (data !== null) {
|
|
62
|
+
console.log(prefix, message, data);
|
|
63
|
+
} else {
|
|
64
|
+
console.log(prefix, message);
|
|
65
|
+
}
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
/**
|
|
57
|
-
* Generate or retrieve a
|
|
69
|
+
* Generate or retrieve a tab-specific client ID
|
|
70
|
+
* Uses sessionStorage (unique per tab) not localStorage (shared across tabs)
|
|
58
71
|
*/
|
|
59
72
|
generateClientId() {
|
|
60
73
|
let id = null;
|
|
61
74
|
|
|
62
75
|
try {
|
|
63
|
-
id =
|
|
76
|
+
id = sessionStorage.getItem('livesync-client-id');
|
|
64
77
|
} catch (e) {
|
|
65
|
-
//
|
|
78
|
+
// sessionStorage might not be available
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
if (!id) {
|
|
69
82
|
id = Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
|
|
70
83
|
try {
|
|
71
|
-
|
|
84
|
+
sessionStorage.setItem('livesync-client-id', id);
|
|
72
85
|
} catch (e) {
|
|
73
86
|
// That's okay
|
|
74
87
|
}
|
|
@@ -98,8 +111,7 @@ class LiveSync {
|
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
// Reset state for new connection
|
|
101
|
-
this.
|
|
102
|
-
this.lastBodyHtml = null;
|
|
114
|
+
this.lastHtml = null;
|
|
103
115
|
|
|
104
116
|
console.log('[LiveSync] Starting for:', this.currentFile);
|
|
105
117
|
this.connect();
|
|
@@ -179,6 +191,12 @@ class LiveSync {
|
|
|
179
191
|
this.sse.onmessage = (event) => {
|
|
180
192
|
const data = JSON.parse(event.data);
|
|
181
193
|
|
|
194
|
+
// Handle notifications (show toast, don't morph)
|
|
195
|
+
if (data.type === "notification") {
|
|
196
|
+
this.handleNotification(data);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
182
200
|
// Handle error events from server
|
|
183
201
|
if (data.error) {
|
|
184
202
|
console.error('[LiveSync] Server error:', data.error);
|
|
@@ -186,32 +204,23 @@ class LiveSync {
|
|
|
186
204
|
return;
|
|
187
205
|
}
|
|
188
206
|
|
|
189
|
-
const {
|
|
207
|
+
const { html, sender } = data;
|
|
190
208
|
|
|
191
209
|
// Ignore own changes
|
|
192
|
-
if (sender === this.clientId)
|
|
193
|
-
|
|
194
|
-
// Guard against invalid body - never apply non-string
|
|
195
|
-
if (typeof body !== 'string') {
|
|
196
|
-
console.error('[LiveSync] Received invalid body (not a string), ignoring');
|
|
210
|
+
if (sender === this.clientId) {
|
|
211
|
+
this._log('Ignoring own message (sender matches clientId)');
|
|
197
212
|
return;
|
|
198
213
|
}
|
|
199
214
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (this.lastHeadHash && headHash !== this.lastHeadHash) {
|
|
205
|
-
console.log('[LiveSync] Head changed, reloading');
|
|
206
|
-
location.reload();
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
this.lastHeadHash = headHash;
|
|
215
|
+
// Guard against invalid html
|
|
216
|
+
if (typeof html !== 'string') {
|
|
217
|
+
console.error('[LiveSync] Received invalid html, ignoring');
|
|
218
|
+
return;
|
|
210
219
|
}
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
this.applyUpdate(
|
|
214
|
-
if (this.onUpdate) this.onUpdate({
|
|
221
|
+
this._log(`Received update from: ${sender} (my clientId: ${this.clientId})`);
|
|
222
|
+
this.applyUpdate(html);
|
|
223
|
+
if (this.onUpdate) this.onUpdate({ html, sender });
|
|
215
224
|
};
|
|
216
225
|
|
|
217
226
|
// Native EventSource auto-reconnects on transient errors
|
|
@@ -229,151 +238,116 @@ class LiveSync {
|
|
|
229
238
|
|
|
230
239
|
/**
|
|
231
240
|
* Listen for snapshot-ready events from the save system.
|
|
232
|
-
* Receives the full cloned documentElement and
|
|
241
|
+
* Receives the full cloned documentElement and sends it.
|
|
233
242
|
*/
|
|
234
243
|
listenForSnapshots() {
|
|
235
|
-
this._snapshotHandler =
|
|
236
|
-
if (this.isPaused)
|
|
244
|
+
this._snapshotHandler = (event) => {
|
|
245
|
+
if (this.isPaused) {
|
|
246
|
+
this._log('snapshot-ready received but isPaused, skipping');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
237
249
|
|
|
238
250
|
const { documentElement } = event.detail;
|
|
239
251
|
if (!documentElement) return;
|
|
240
252
|
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Compute headHash using SHA-256 (async)
|
|
246
|
-
const headHash = await this.computeHeadHash(head);
|
|
247
|
-
|
|
248
|
-
// Send update even if body is empty (allows clearing content)
|
|
249
|
-
this.sendUpdate(body, headHash);
|
|
253
|
+
this._log('snapshot-ready received, preparing to send');
|
|
254
|
+
const html = documentElement.outerHTML;
|
|
255
|
+
this.sendUpdate(html);
|
|
250
256
|
};
|
|
251
257
|
|
|
252
258
|
document.addEventListener('hyperclay:snapshot-ready', this._snapshotHandler);
|
|
253
259
|
}
|
|
254
260
|
|
|
255
261
|
/**
|
|
256
|
-
* Send
|
|
257
|
-
* Only updates
|
|
262
|
+
* Send full HTML to the server (debounced)
|
|
263
|
+
* Only updates lastHtml after successful save
|
|
258
264
|
*/
|
|
259
|
-
sendUpdate(
|
|
265
|
+
sendUpdate(html) {
|
|
260
266
|
clearTimeout(this.debounceTimer);
|
|
261
267
|
|
|
262
268
|
this.debounceTimer = setTimeout(() => {
|
|
263
269
|
// Skip if unchanged
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
270
|
+
if (html === this.lastHtml) {
|
|
271
|
+
this._log('Skipping send - HTML unchanged');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
267
274
|
|
|
268
|
-
|
|
269
|
-
this.lastHeadHash = headHash;
|
|
275
|
+
this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
|
|
270
276
|
|
|
271
277
|
fetch('/live-sync/save', {
|
|
272
278
|
method: 'POST',
|
|
273
279
|
headers: { 'Content-Type': 'application/json' },
|
|
274
280
|
body: JSON.stringify({
|
|
275
281
|
file: this.currentFile,
|
|
276
|
-
|
|
277
|
-
sender: this.clientId
|
|
278
|
-
headHash: headHash
|
|
282
|
+
html: html,
|
|
283
|
+
sender: this.clientId
|
|
279
284
|
})
|
|
280
285
|
}).then(response => {
|
|
281
286
|
if (response.ok) {
|
|
282
|
-
|
|
283
|
-
this.lastBodyHtml = body;
|
|
287
|
+
this.lastHtml = html;
|
|
284
288
|
} else {
|
|
285
|
-
// Log non-OK responses but don't suppress future sends
|
|
286
289
|
console.warn('[LiveSync] Save returned status:', response.status);
|
|
287
290
|
}
|
|
288
291
|
}).catch(err => {
|
|
289
|
-
// Network error - don't update lastBodyHtml so next mutation will retry
|
|
290
292
|
console.error('[LiveSync] Save failed:', err);
|
|
291
293
|
if (this.onError) this.onError(err);
|
|
292
294
|
});
|
|
293
295
|
}, this.debounceMs);
|
|
294
296
|
}
|
|
295
297
|
|
|
296
|
-
/**
|
|
297
|
-
* Compute SHA-256 hash of head content (first 16 hex chars)
|
|
298
|
-
* Uses SubtleCrypto API (async)
|
|
299
|
-
* @param {string} head - Head innerHTML
|
|
300
|
-
* @returns {Promise<string|null>} 16-char hex hash or null if unavailable
|
|
301
|
-
*/
|
|
302
|
-
async computeHeadHash(head) {
|
|
303
|
-
if (!head) return null;
|
|
304
|
-
|
|
305
|
-
// SubtleCrypto requires secure context (HTTPS or localhost)
|
|
306
|
-
if (!crypto?.subtle?.digest) {
|
|
307
|
-
console.warn('[LiveSync] SHA-256 unavailable (non-secure context), skipping headHash');
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
const encoder = new TextEncoder();
|
|
313
|
-
const data = encoder.encode(head);
|
|
314
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
315
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
316
|
-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16);
|
|
317
|
-
} catch (e) {
|
|
318
|
-
console.warn('[LiveSync] SHA-256 hash failed, skipping headHash:', e);
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
298
|
/**
|
|
324
299
|
* Apply an update received from the server
|
|
325
|
-
*
|
|
300
|
+
* Morphs the entire document (head and body)
|
|
326
301
|
*/
|
|
327
|
-
applyUpdate(
|
|
328
|
-
|
|
329
|
-
if (typeof bodyHtml !== 'string') {
|
|
330
|
-
console.error('[LiveSync] applyUpdate called with non-string value, ignoring');
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
302
|
+
applyUpdate(html) {
|
|
303
|
+
this._log('applyUpdate - pausing mutations and morphing');
|
|
334
304
|
this.isPaused = true;
|
|
335
|
-
this.
|
|
305
|
+
this.lastHtml = html;
|
|
336
306
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
temp.innerHTML = bodyHtml;
|
|
307
|
+
// Pause mutation observer so morph doesn't trigger autosave
|
|
308
|
+
Mutation.pause();
|
|
340
309
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
});
|
|
310
|
+
// Parse as full document
|
|
311
|
+
const parser = new DOMParser();
|
|
312
|
+
const newDoc = parser.parseFromString(html, 'text/html');
|
|
345
313
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
314
|
+
// Morph entire document (html element)
|
|
315
|
+
HyperMorph.morph(document.documentElement, newDoc.documentElement, {
|
|
316
|
+
morphStyle: 'outerHTML',
|
|
317
|
+
ignoreActiveValue: true,
|
|
318
|
+
head: { style: 'merge' },
|
|
319
|
+
scripts: { handle: true, matchMode: 'smart' }
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
this._log('applyUpdate - morph complete, resuming mutations');
|
|
323
|
+
Mutation.resume();
|
|
324
|
+
this.isPaused = false;
|
|
350
325
|
}
|
|
351
326
|
|
|
352
327
|
/**
|
|
353
|
-
*
|
|
328
|
+
* Handle a notification message from the server
|
|
329
|
+
* Shows a toast and emits an event for custom handling
|
|
330
|
+
* @param {Object} data - { msgType, msg, action? }
|
|
354
331
|
*/
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
332
|
+
handleNotification({ msgType, msg, action }) {
|
|
333
|
+
this._log(`Notification received: ${msgType} - ${msg}`);
|
|
334
|
+
|
|
335
|
+
// Show toast if available
|
|
336
|
+
if (window.toast) {
|
|
337
|
+
window.toast(msg, msgType);
|
|
338
|
+
} else {
|
|
339
|
+
console.log(`[LiveSync] Notification: ${msg}`);
|
|
340
|
+
}
|
|
363
341
|
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
342
|
+
// Emit event for custom handling (e.g., reload button)
|
|
343
|
+
document.dispatchEvent(new CustomEvent('hyperclay:notification', {
|
|
344
|
+
detail: { msgType, msg, action }
|
|
345
|
+
}));
|
|
369
346
|
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
opt.selected = opt.hasAttribute('selected');
|
|
375
|
-
});
|
|
376
|
-
});
|
|
347
|
+
// Call notification callback if set
|
|
348
|
+
if (this.onNotification) {
|
|
349
|
+
this.onNotification({ msgType, msg, action });
|
|
350
|
+
}
|
|
377
351
|
}
|
|
378
352
|
|
|
379
353
|
/**
|
package/src/core/autosave.js
CHANGED
|
@@ -27,9 +27,23 @@ function initSavePageOnChange() {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Initialize auto-save on input events for [persist] elements
|
|
32
|
+
* Form input values don't trigger DOM mutations, so we listen for input events
|
|
33
|
+
*/
|
|
34
|
+
let inputSaveTimer = null;
|
|
35
|
+
function initSaveOnPersistInput() {
|
|
36
|
+
document.addEventListener('input', (e) => {
|
|
37
|
+
if (!e.target.closest('[persist]')) return;
|
|
38
|
+
clearTimeout(inputSaveTimer);
|
|
39
|
+
inputSaveTimer = setTimeout(savePageThrottled, 3333);
|
|
40
|
+
}, true);
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
function init() {
|
|
31
44
|
if (!isEditMode) return;
|
|
32
45
|
initSavePageOnChange();
|
|
46
|
+
initSaveOnPersistInput();
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
// No window exports - savePageThrottled is exported from save-system
|
package/src/core/savePageCore.js
CHANGED
|
@@ -112,12 +112,31 @@ export function savePage(callback = () => {}) {
|
|
|
112
112
|
const controller = new AbortController();
|
|
113
113
|
const timeoutId = setTimeout(() => controller.abort(), 12000);
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
// Check if running on Hyperclay Local - send JSON with both versions for platform sync
|
|
116
|
+
const isHyperclayLocal = window.location.hostname === 'localhost' ||
|
|
117
|
+
window.location.hostname === '127.0.0.1';
|
|
118
|
+
|
|
119
|
+
const fetchOptions = {
|
|
116
120
|
method: 'POST',
|
|
117
121
|
credentials: 'include',
|
|
118
|
-
body: currentContents,
|
|
119
122
|
signal: controller.signal
|
|
120
|
-
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (isHyperclayLocal && window.__hyperclaySnapshotHtml) {
|
|
126
|
+
// Send JSON with both stripped content and full snapshot for platform live sync
|
|
127
|
+
fetchOptions.headers = { 'Content-Type': 'application/json' };
|
|
128
|
+
fetchOptions.body = JSON.stringify({
|
|
129
|
+
content: currentContents,
|
|
130
|
+
snapshotHtml: window.__hyperclaySnapshotHtml
|
|
131
|
+
});
|
|
132
|
+
// Clear after use to avoid stale data
|
|
133
|
+
window.__hyperclaySnapshotHtml = null;
|
|
134
|
+
} else {
|
|
135
|
+
// Platform: send plain text as before
|
|
136
|
+
fetchOptions.body = currentContents;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fetch(saveEndpoint, fetchOptions)
|
|
121
140
|
.then(res => {
|
|
122
141
|
clearTimeout(timeoutId);
|
|
123
142
|
return res.json().then(data => {
|
|
@@ -187,12 +206,31 @@ export function saveHtml(html, callback = () => {}) {
|
|
|
187
206
|
const controller = new AbortController();
|
|
188
207
|
const timeoutId = setTimeout(() => controller.abort(), 12000);
|
|
189
208
|
|
|
190
|
-
|
|
209
|
+
// Check if running on Hyperclay Local - send JSON with both versions for platform sync
|
|
210
|
+
const isHyperclayLocal = window.location.hostname === 'localhost' ||
|
|
211
|
+
window.location.hostname === '127.0.0.1';
|
|
212
|
+
|
|
213
|
+
const fetchOptions = {
|
|
191
214
|
method: 'POST',
|
|
192
215
|
credentials: 'include',
|
|
193
|
-
body: html,
|
|
194
216
|
signal: controller.signal
|
|
195
|
-
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (isHyperclayLocal && window.__hyperclaySnapshotHtml) {
|
|
220
|
+
// Send JSON with both stripped content and full snapshot for platform live sync
|
|
221
|
+
fetchOptions.headers = { 'Content-Type': 'application/json' };
|
|
222
|
+
fetchOptions.body = JSON.stringify({
|
|
223
|
+
content: html,
|
|
224
|
+
snapshotHtml: window.__hyperclaySnapshotHtml
|
|
225
|
+
});
|
|
226
|
+
// Clear after use to avoid stale data
|
|
227
|
+
window.__hyperclaySnapshotHtml = null;
|
|
228
|
+
} else {
|
|
229
|
+
// Platform: send plain text as before
|
|
230
|
+
fetchOptions.body = html;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fetch(saveEndpoint, fetchOptions)
|
|
196
234
|
.then(res => {
|
|
197
235
|
clearTimeout(timeoutId);
|
|
198
236
|
return res.json().then(data => {
|
package/src/core/snapshot.js
CHANGED
|
@@ -172,6 +172,14 @@ export function captureForSaveAndComparison({ emitForSync = true } = {}) {
|
|
|
172
172
|
document.dispatchEvent(new CustomEvent('hyperclay:snapshot-ready', {
|
|
173
173
|
detail: { documentElement: clone }
|
|
174
174
|
}));
|
|
175
|
+
|
|
176
|
+
// Store snapshot HTML for Hyperclay Local platform sync
|
|
177
|
+
// This allows the save system to send both stripped and full versions
|
|
178
|
+
const isHyperclayLocal = window.location.hostname === 'localhost' ||
|
|
179
|
+
window.location.hostname === '127.0.0.1';
|
|
180
|
+
if (isHyperclayLocal) {
|
|
181
|
+
window.__hyperclaySnapshotHtml = '<!DOCTYPE html>' + clone.outerHTML;
|
|
182
|
+
}
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
// Run inline [onbeforesave] handlers
|
|
@@ -1,15 +1,56 @@
|
|
|
1
1
|
import { insertStyles } from '../dom-utilities/insertStyleTag.js';
|
|
2
2
|
import cookie from '../utilities/cookie.js';
|
|
3
3
|
|
|
4
|
+
function findTailwindLink(resourceName) {
|
|
5
|
+
const targetPath = `/tailwindcss/${resourceName}.css`;
|
|
6
|
+
return [...document.querySelectorAll('link[rel="stylesheet"]')]
|
|
7
|
+
.find(el => {
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(el.getAttribute('href'), location.href);
|
|
10
|
+
return url.pathname === targetPath;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function swapTailwindLink() {
|
|
18
|
+
const currentResource = cookie.get('currentResource');
|
|
19
|
+
if (!currentResource) return;
|
|
20
|
+
|
|
21
|
+
const oldLink = findTailwindLink(currentResource);
|
|
22
|
+
if (!oldLink) return;
|
|
23
|
+
|
|
24
|
+
const newLink = document.createElement('link');
|
|
25
|
+
newLink.rel = 'stylesheet';
|
|
26
|
+
const url = new URL(oldLink.getAttribute('href'), location.href);
|
|
27
|
+
url.searchParams.set('v', Date.now());
|
|
28
|
+
newLink.href = url.href;
|
|
29
|
+
newLink.setAttribute('save-ignore', '');
|
|
30
|
+
|
|
31
|
+
oldLink.insertAdjacentElement('afterend', newLink);
|
|
32
|
+
|
|
33
|
+
newLink.onload = () => {
|
|
34
|
+
oldLink.remove();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
if (oldLink.parentNode) {
|
|
39
|
+
oldLink.remove();
|
|
40
|
+
}
|
|
41
|
+
}, 2000);
|
|
42
|
+
}
|
|
43
|
+
|
|
4
44
|
function init() {
|
|
5
45
|
const currentResource = cookie.get('currentResource');
|
|
6
46
|
if (!currentResource) return;
|
|
7
47
|
|
|
8
48
|
const href = `/tailwindcss/${currentResource}.css`;
|
|
9
49
|
insertStyles(href, (link) => {
|
|
10
|
-
link.setAttribute('
|
|
11
|
-
link.setAttribute('mutations-ignore', '');
|
|
50
|
+
link.setAttribute('save-ignore', '');
|
|
12
51
|
});
|
|
52
|
+
|
|
53
|
+
document.addEventListener('hyperclay:save-saved', swapTailwindLink);
|
|
13
54
|
}
|
|
14
55
|
|
|
15
56
|
init();
|
|
@@ -62,14 +62,24 @@ function insertStyles(nameOrHref, cssOrCallback, callback) {
|
|
|
62
62
|
const href = nameOrHref;
|
|
63
63
|
const cb = typeof cssOrCallback === 'function' ? cssOrCallback : undefined;
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
const
|
|
65
|
+
// Helper to get base URL without query params (for comparison)
|
|
66
|
+
const getBaseUrl = (url) => {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = new URL(url, window.location.href);
|
|
69
|
+
return parsed.origin + parsed.pathname;
|
|
70
|
+
} catch {
|
|
71
|
+
return url;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Normalize href to full URL path (without query params) for comparison
|
|
76
|
+
const normalizedHref = getBaseUrl(href);
|
|
67
77
|
|
|
68
|
-
// Find all links with matching normalized path
|
|
78
|
+
// Find all links with matching normalized path (ignoring query params like ?v=)
|
|
69
79
|
const existingLinks = [...document.querySelectorAll('link[rel="stylesheet"]')]
|
|
70
80
|
.filter(el => {
|
|
71
81
|
try {
|
|
72
|
-
return
|
|
82
|
+
return getBaseUrl(el.getAttribute('href')) === normalizedHref;
|
|
73
83
|
} catch {
|
|
74
84
|
return false;
|
|
75
85
|
}
|
package/src/hyperclay.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HyperclayJS v1.
|
|
2
|
+
* HyperclayJS v1.18.0 - Minimal Browser-Native Loader
|
|
3
3
|
*
|
|
4
4
|
* Modules auto-init when imported (no separate init call needed).
|
|
5
5
|
* Include `export-to-window` feature to export to window.hyperclay.
|
|
@@ -123,7 +123,6 @@ const PRESETS = {
|
|
|
123
123
|
"onaftersave",
|
|
124
124
|
"dialogs",
|
|
125
125
|
"toast",
|
|
126
|
-
"toast-hyperclay",
|
|
127
126
|
"the-modal",
|
|
128
127
|
"mutation",
|
|
129
128
|
"nearest",
|
|
@@ -288,3 +287,37 @@ export const uploadFile = window.hyperclayModules['file-upload']?.uploadFile ??
|
|
|
288
287
|
export const createFile = window.hyperclayModules['file-upload']?.createFile ?? window.hyperclayModules['file-upload']?.default;
|
|
289
288
|
export const uploadFileBasic = window.hyperclayModules['file-upload']?.uploadFileBasic ?? window.hyperclayModules['file-upload']?.default;
|
|
290
289
|
export const liveSync = window.hyperclayModules['live-sync']?.liveSync ?? window.hyperclayModules['live-sync']?.default;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Enable debug logging across all hyperclay modules that support it.
|
|
293
|
+
* @param {boolean} [enabled=true] - Whether to enable or disable debug logging
|
|
294
|
+
*/
|
|
295
|
+
export function setDebug(enabled = true) {
|
|
296
|
+
const modules = [
|
|
297
|
+
{ name: 'Mutation', obj: window.hyperclayModules['mutation']?.default },
|
|
298
|
+
{ name: 'LiveSync', obj: window.hyperclayModules['live-sync']?.liveSync },
|
|
299
|
+
{ name: 'OptionVisibility', obj: window.hyperclayModules['option-visibility']?.default },
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
const enabledModules = [];
|
|
303
|
+
for (const { name, obj } of modules) {
|
|
304
|
+
if (obj && 'debug' in obj) {
|
|
305
|
+
obj.debug = enabled;
|
|
306
|
+
enabledModules.push(name);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log(`[hyperclay] Debug ${enabled ? 'enabled' : 'disabled'} for:`, enabledModules.join(', ') || 'no modules found');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Export debug to window.hyperclay
|
|
314
|
+
if (!window.__hyperclayNoAutoExport) {
|
|
315
|
+
window.hyperclay = window.hyperclay || {};
|
|
316
|
+
window.hyperclay.debug = setDebug;
|
|
317
|
+
window.h = window.hyperclay;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Auto-enable debug for all modules if ?debug=true was passed
|
|
321
|
+
if (debug) {
|
|
322
|
+
setDebug(true);
|
|
323
|
+
}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Toast Hyperclay -
|
|
2
|
+
* Toast Hyperclay - Configure toast() to use Hyperclay platform styling
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* When this module is loaded, it overrides the default toast styling so that
|
|
5
|
+
* all toast() calls (including from save-toast) use Hyperclay styling.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* the Hyperclay platform for backward compatibility.
|
|
7
|
+
* Also provides toastHyperclay() for backward compatibility.
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
toastCore,
|
|
13
12
|
injectToastStyles,
|
|
13
|
+
setToastTheme,
|
|
14
14
|
hyperclayStyles,
|
|
15
15
|
hyperclayTemplates,
|
|
16
16
|
hyperclayIcons
|
|
17
17
|
} from './toast.js';
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// Configure the base toast() to use hyperclay styling
|
|
20
|
+
setToastTheme({
|
|
21
|
+
styles: hyperclayStyles,
|
|
22
|
+
templates: hyperclayTemplates,
|
|
23
|
+
icons: hyperclayIcons,
|
|
24
|
+
theme: 'hyperclay'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Toast function with Hyperclay styling (kept for backward compatibility)
|
|
20
28
|
function toastHyperclay(message, messageType = "success") {
|
|
21
29
|
injectToastStyles(hyperclayStyles, 'hyperclay');
|
|
22
30
|
toastCore(message, messageType, {
|
package/src/ui/toast.js
CHANGED
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
// Default modern icons (normalized to 48x48 viewBox)
|
|
5
5
|
const defaultIcons = {
|
|
6
6
|
success: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9404 23.9475L21.9099 31.224L35.1906 15.9045M3 4.5H44.9804V44.309H3V4.5Z" stroke="#33D131" stroke-width="4.3"/></svg>`,
|
|
7
|
-
error: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32.7383 14.4045L14 33.1429M32.7451 33.1429L14.0068 14.4046M3.01 4H44.99V43.809H3.01V4Z" stroke="#FF4450" stroke-width="4"/></svg
|
|
7
|
+
error: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32.7383 14.4045L14 33.1429M32.7451 33.1429L14.0068 14.4046M3.01 4H44.99V43.809H3.01V4Z" stroke="#FF4450" stroke-width="4"/></svg>`,
|
|
8
|
+
warning: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 8L4 40H44L24 8Z" stroke="#F5A623" stroke-width="4" stroke-linejoin="round"/><path d="M24 20V28M24 34V36" stroke="#F5A623" stroke-width="4" stroke-linecap="round"/></svg>`,
|
|
9
|
+
info: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="24" r="20" stroke="#4A90D9" stroke-width="4"/><path d="M24 20V32M24 14V16" stroke="#4A90D9" stroke-width="4" stroke-linecap="round"/></svg>`
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
// Hyperclay icons
|
|
11
13
|
export const hyperclayIcons = {
|
|
12
14
|
success: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M32 1h-5v3.5h-2.5V8h-2v3.5H20V15h-2.5v3.5h-2V22H13v3.5H9V22H7v-3.5H6V15H1v3.5h1V22h2v3.5h1.5V29H7v3.5h5V29h3.5v-3.5H18V22h2.5v-3.5h2V15H25v-3.5h2.5V8h2V4.5H32V1Z" fill="#76C824"/></svg>`,
|
|
13
|
-
error: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M33 1h-5v3.5h-3.5V8H21v3.5h-3.5V15h-2v-3.5H12V8H8.5V4.5H5V1H0v3.5h3.5V8H7v3.5h3.5V15H14v3.5h-3.5V22H7v3.5H3.5V29H0v3.5h5V29h3.5v-3.5H12V22h3.5v-3.5h2V22H21v3.5h3.5V29H28v3.5h5V29h-3.5v-3.5H26V22h-3.5v-3.5H19V15h3.5v-3.5H26V8h3.5V4.5H33V1Z" fill="#DD304F"/></svg
|
|
15
|
+
error: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M33 1h-5v3.5h-3.5V8H21v3.5h-3.5V15h-2v-3.5H12V8H8.5V4.5H5V1H0v3.5h3.5V8H7v3.5h3.5V15H14v3.5h-3.5V22H7v3.5H3.5V29H0v3.5h5V29h3.5v-3.5H12V22h3.5v-3.5h2V22H21v3.5h3.5V29H28v3.5h5V29h-3.5v-3.5H26V22h-3.5v-3.5H19V15h3.5v-3.5H26V8h3.5V4.5H33V1Z" fill="#DD304F"/></svg>`,
|
|
16
|
+
warning: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M18 1h-3v3h-2v3h-2v3H9v3H7v3H5v3H3v3H1v6h31v-6h-2v-3h-2v-3h-2v-3h-2v-3h-2V7h-2V4h-2V1zm-2 8h1v10h-1V9zm0 13h1v2h-1v-2z" fill="#F5A623"/></svg>`,
|
|
17
|
+
info: `<svg viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 1v3H9v3H6v3H3v6h3v3h3v3h3v3h9v-3h3v-3h3v-3h3v-6h-3V7h-3V4h-3V1H12zm4 7h1v2h-1V8zm0 5h1v9h-1v-9z" fill="#4A90D9"/></svg>`
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
// Default templates
|
|
@@ -27,6 +31,18 @@ const defaultTemplates = {
|
|
|
27
31
|
<div class="toast-icon">{icon}</div>
|
|
28
32
|
<div class="toast-message">{message}</div>
|
|
29
33
|
</div>
|
|
34
|
+
`,
|
|
35
|
+
warning: `
|
|
36
|
+
<div class="toast hide warning noise-texture">
|
|
37
|
+
<div class="toast-icon">{icon}</div>
|
|
38
|
+
<div class="toast-message">{message}</div>
|
|
39
|
+
</div>
|
|
40
|
+
`,
|
|
41
|
+
info: `
|
|
42
|
+
<div class="toast hide info noise-texture">
|
|
43
|
+
<div class="toast-icon">{icon}</div>
|
|
44
|
+
<div class="toast-message">{message}</div>
|
|
45
|
+
</div>
|
|
30
46
|
`
|
|
31
47
|
}
|
|
32
48
|
};
|
|
@@ -45,6 +61,18 @@ export const hyperclayTemplates = {
|
|
|
45
61
|
{icon}
|
|
46
62
|
<span class="message">{message}</span>
|
|
47
63
|
</div>
|
|
64
|
+
`,
|
|
65
|
+
warning: `
|
|
66
|
+
<div class="toast hide warning">
|
|
67
|
+
{icon}
|
|
68
|
+
<span class="message">{message}</span>
|
|
69
|
+
</div>
|
|
70
|
+
`,
|
|
71
|
+
info: `
|
|
72
|
+
<div class="toast hide info">
|
|
73
|
+
{icon}
|
|
74
|
+
<span class="message">{message}</span>
|
|
75
|
+
</div>
|
|
48
76
|
`
|
|
49
77
|
}
|
|
50
78
|
};
|
|
@@ -94,6 +122,16 @@ const modernStyles = `
|
|
|
94
122
|
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #240A13 0%, #481826 100%);
|
|
95
123
|
}
|
|
96
124
|
|
|
125
|
+
[data-toast-theme="modern"] .toast.warning {
|
|
126
|
+
border-color: #B8860B;
|
|
127
|
+
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #2A2010 0%, #3D3018 100%);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[data-toast-theme="modern"] .toast.info {
|
|
131
|
+
border-color: #2E6B9E;
|
|
132
|
+
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #0A1A24 0%, #162D3D 100%);
|
|
133
|
+
}
|
|
134
|
+
|
|
97
135
|
[data-toast-theme="modern"] .toast-icon {
|
|
98
136
|
position: relative;
|
|
99
137
|
top: -1px;
|
|
@@ -180,11 +218,34 @@ export const hyperclayStyles = `
|
|
|
180
218
|
color: #DD304F;
|
|
181
219
|
border: 2px dashed #CD2140;
|
|
182
220
|
}
|
|
221
|
+
|
|
222
|
+
[data-toast-theme="hyperclay"] .toast.warning {
|
|
223
|
+
color: #F5A623;
|
|
224
|
+
border: 2px dashed #D4900E;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[data-toast-theme="hyperclay"] .toast.info {
|
|
228
|
+
color: #4A90D9;
|
|
229
|
+
border: 2px dashed #3A7BBF;
|
|
230
|
+
}
|
|
183
231
|
`;
|
|
184
232
|
|
|
185
233
|
// Track which theme styles have been injected
|
|
186
234
|
const injectedThemes = new Set();
|
|
187
235
|
|
|
236
|
+
// Global toast configuration (can be overridden by toast-hyperclay module)
|
|
237
|
+
let toastConfig = {
|
|
238
|
+
styles: modernStyles,
|
|
239
|
+
templates: defaultTemplates,
|
|
240
|
+
icons: defaultIcons,
|
|
241
|
+
theme: 'modern'
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Allow other modules (like toast-hyperclay) to override default toast styling
|
|
245
|
+
export function setToastTheme(config) {
|
|
246
|
+
Object.assign(toastConfig, config);
|
|
247
|
+
}
|
|
248
|
+
|
|
188
249
|
// Helper function to inject styles for a theme (additive, not replacing)
|
|
189
250
|
export function injectToastStyles(styles, theme) {
|
|
190
251
|
if (injectedThemes.has(theme)) return;
|
|
@@ -242,13 +303,13 @@ export function toastCore(message, messageType = "success", config = {}) {
|
|
|
242
303
|
}, 6600);
|
|
243
304
|
}
|
|
244
305
|
|
|
245
|
-
// Main toast function - uses modern
|
|
306
|
+
// Main toast function - uses configured theme (default: modern, or hyperclay if toast-hyperclay loaded)
|
|
246
307
|
function toast(message, messageType = "success") {
|
|
247
|
-
injectToastStyles(
|
|
308
|
+
injectToastStyles(toastConfig.styles, toastConfig.theme);
|
|
248
309
|
toastCore(message, messageType, {
|
|
249
|
-
templates:
|
|
250
|
-
icons:
|
|
251
|
-
theme:
|
|
310
|
+
templates: toastConfig.templates,
|
|
311
|
+
icons: toastConfig.icons,
|
|
312
|
+
theme: toastConfig.theme
|
|
252
313
|
});
|
|
253
314
|
}
|
|
254
315
|
|
|
@@ -67,8 +67,27 @@ const Mutation = {
|
|
|
67
67
|
},
|
|
68
68
|
|
|
69
69
|
_observing: false,
|
|
70
|
+
_paused: false,
|
|
70
71
|
debug: false,
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Pause mutation observation.
|
|
75
|
+
* Use this when making programmatic DOM changes that shouldn't trigger callbacks.
|
|
76
|
+
* Always pair with resume() in a try/finally block.
|
|
77
|
+
*/
|
|
78
|
+
pause() {
|
|
79
|
+
this._paused = true;
|
|
80
|
+
this._log('Paused');
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Resume mutation observation after a pause.
|
|
85
|
+
*/
|
|
86
|
+
resume() {
|
|
87
|
+
this._paused = false;
|
|
88
|
+
this._log('Resumed');
|
|
89
|
+
},
|
|
90
|
+
|
|
72
91
|
_log(message, data = null, type = 'log') {
|
|
73
92
|
if (!this.debug) return;
|
|
74
93
|
|
|
@@ -173,7 +192,10 @@ const Mutation = {
|
|
|
173
192
|
}
|
|
174
193
|
},
|
|
175
194
|
|
|
176
|
-
_shouldIgnore(
|
|
195
|
+
_shouldIgnore(node) {
|
|
196
|
+
// For non-element nodes (like text nodes), start from parent
|
|
197
|
+
let element = (node && node.nodeType !== 1) ? node.parentElement : node;
|
|
198
|
+
|
|
177
199
|
while (element && element.nodeType === 1) {
|
|
178
200
|
if (element.hasAttribute?.('mutations-ignore') ||
|
|
179
201
|
element.hasAttribute?.('save-remove') ||
|
|
@@ -186,8 +208,12 @@ const Mutation = {
|
|
|
186
208
|
},
|
|
187
209
|
|
|
188
210
|
_handleMutations(mutations) {
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
// Skip all mutations while paused (e.g., during live-sync morph)
|
|
212
|
+
if (this._paused) {
|
|
213
|
+
this._log(`Skipping ${mutations.length} mutations (paused)`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
191
217
|
const changes = [];
|
|
192
218
|
const changesByType = {
|
|
193
219
|
add: [],
|
|
@@ -199,7 +225,6 @@ const Mutation = {
|
|
|
199
225
|
for (const mutation of mutations) {
|
|
200
226
|
// Check if the target or any parent has mutations-ignore attribute
|
|
201
227
|
if (this._shouldIgnore(mutation.target)) {
|
|
202
|
-
this._log('Ignoring mutation due to mutations-ignore attribute', { mutation });
|
|
203
228
|
continue;
|
|
204
229
|
}
|
|
205
230
|
|
|
@@ -246,7 +271,7 @@ const Mutation = {
|
|
|
246
271
|
}
|
|
247
272
|
|
|
248
273
|
for (const node of mutation.removedNodes) {
|
|
249
|
-
if (node.nodeType === 1 && !
|
|
274
|
+
if (node.nodeType === 1 && !this._shouldIgnore(node)) {
|
|
250
275
|
const removedNodes = [node, ...node.querySelectorAll('*')];
|
|
251
276
|
this._log(`Processing ${removedNodes.length} removed nodes`, { removedNodes });
|
|
252
277
|
|
|
@@ -311,8 +336,6 @@ const Mutation = {
|
|
|
311
336
|
if (changesByType.attribute.length) {
|
|
312
337
|
this._notify('attribute', changesByType.attribute);
|
|
313
338
|
}
|
|
314
|
-
} else {
|
|
315
|
-
this._log('No changes to process after filtering');
|
|
316
339
|
}
|
|
317
340
|
},
|
|
318
341
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var HyperMorph=(()=>{var q=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var Y=(s,a)=>{for(var u in a)q(s,u,{get:a[u],enumerable:!0})},J=(s,a,u,d)=>{if(a&&typeof a=="object"||typeof a=="function")for(let f of U(a))!G.call(s,f)&&f!==u&&q(s,f,{get:()=>a[f],enumerable:!(d=z(a,f))||d.enumerable});return s};var K=s=>J(q({},"__esModule",{value:!0}),s);var he={};Y(he,{HyperMorph:()=>V,default:()=>fe,defaults:()=>de,morph:()=>ce});var B={includeClasses:!0,includeAttributes:["href","src","name","type","role","aria-label","alt","title"],excludeAttributePrefixes:["data-morph-","data-hyper-","data-im-"],textHintLength:64,excludeIds:!0,maxPathDepth:4,landmarks:["HEADER","NAV","MAIN","ASIDE","FOOTER","SECTION","ARTICLE"],weights:{signature:100,pathSegment:10,textMatch:20,textMismatch:25,uniqueCandidate:50,positionPenalty:1},minConfidence:101};function Q(s){let a=5381;for(let u=0;u<s.length;u++)a=(a<<5)+a^s.charCodeAt(u);return Math.abs(a).toString(36)}function X(s){if(s.classList&&s.classList.length>0)return Array.from(s.classList).sort().join(" ");let a=s.getAttribute?.("class");return a?a.split(/\s+/).filter(Boolean).sort().join(" "):""}function Z(s,a){let u=[];for(let d of s.attributes||[]){let f=d.name;f==="id"||f==="class"||a.excludeAttributePrefixes.some(y=>f.startsWith(y))||a.includeAttributes.includes(f)&&u.push(`${f}=${d.value}`)}return u.sort().join("|")}function ee(s,a){return(s.textContent||"").replace(/\s+/g," ").trim().slice(0,a.textHintLength)}function te(s,a){let u=[s.tagName];return a.includeClasses&&u.push(X(s)),u.push(Z(s,a)),Q(u.join("|"))}function ne(s){let a=s.tagName,u=1,d=s.previousElementSibling;for(;d;)d.tagName===a&&u++,d=d.previousElementSibling;return u}function re(s,a){return s.id||s.getAttribute?.("role")?!0:a.landmarks.includes(s.tagName)}function se(s){if(s.id)return`#${s.id}`;let a=s.getAttribute?.("role");return a?`@${a}`:s.tagName}function ie(s,a){let u=[],d=s;for(;d&&d.tagName&&u.length<a.maxPathDepth;){let f=`${d.tagName}:${ne(d)}`;if(u.unshift(f),d!==s&&re(d,a)){u.unshift(se(d));break}d=d.parentElement}return u}function ae(s,a){let u=0,d=s.length-1,f=a.length-1;for(;d>=0&&f>=0&&s[d]===a[f];)u++,d--,f--;return u}function R(s,a,u){if(u.has(s))return u.get(s);let d={signature:te(s,a),path:ie(s,a),textHint:ee(s,a)};return u.set(s,d),d}function P(s,a,u,d){if(d.has(s))return d.get(s);let f=new Map,y=s.querySelectorAll("*"),H=0;for(let b of y){let k=R(b,a,u);k.domIndex=H++,f.has(k.signature)||f.set(k.signature,[]),f.get(k.signature).push(b)}return d.set(s,f),f}function oe(s,a,u){u.delete(s),a.delete(s);let d=s.querySelectorAll("*");for(let f of d)a.delete(f)}function F(s,a,u,d,f){let y=R(s,u,d),H=R(a,u,d),b=u.weights,k={},L=0;if(y.signature!==H.signature)return{score:0,breakdown:{rejected:"signature mismatch"}};L+=b.signature,k.signature=b.signature;let I=ae(y.path,H.path)*b.pathSegment;L+=I,k.path=I;let E=!0;if(y.textHint&&H.textHint?y.textHint===H.textHint?(L+=b.textMatch,k.text=b.textMatch):(L-=b.textMismatch,k.text=-b.textMismatch,E=!1):y.textHint!==H.textHint&&(L-=b.textMismatch,k.text=-b.textMismatch,E=!1),f.candidateCount===1&&E&&(L+=b.uniqueCandidate,k.unique=b.uniqueCandidate),typeof y.domIndex=="number"&&typeof H.domIndex=="number"){let x=Math.abs(y.domIndex-H.domIndex),c=Math.min(x*b.positionPenalty,20);L-=c,k.drift=-c}return{score:L,breakdown:k}}function D(s,a,u,d,f){if(u.excludeIds&&s.id)return null;let y=P(a,u,d,f),H=R(s,u,d);if(typeof H.domIndex!="number"){let E=0,x=s.previousElementSibling;for(;x;)E++,x=x.previousElementSibling;H.domIndex=E}let b=y.get(H.signature)||[],k=u.excludeIds?b.filter(E=>!E.id):b;if(k.length===0)return null;let L=null,N=0,I=null;for(let E of k){let{score:x,breakdown:c}=F(s,E,u,d,{candidateCount:k.length});x>N&&(N=x,L=E,I=c)}return N<u.minConfidence?null:{element:L,confidence:N,breakdown:I}}function $(s,a,u,d,f){let y=a.querySelectorAll("*"),H=P(s,u,d,f),b=0;for(let I of y){let E=R(I,u,d);E.domIndex=b++}let k=[];for(let I of y){if(u.excludeIds&&I.id)continue;let E=R(I,u,d),x=H.get(E.signature)||[],c=u.excludeIds?x.filter(S=>!S.id):x;for(let S of c){let{score:p,breakdown:M}=F(I,S,u,d,{candidateCount:c.length});p>=u.minConfidence&&k.push({newEl:I,oldEl:S,score:p,breakdown:M})}}k.sort((I,E)=>E.score-I.score);let L=new Map,N=new Set;for(let{newEl:I,oldEl:E}of k)L.has(I)||N.has(E)||(L.set(I,E),N.add(E));return L}function W(s,a,u,d){let f=R(s,u,d),y=R(a,u,d),{score:H,breakdown:b}=F(s,a,u,d,{candidateCount:1});return{matches:H>=u.minConfidence,score:H,breakdown:b,newMeta:{signature:f.signature,path:f.path,textHint:f.textHint},oldMeta:{signature:y.signature,path:y.path,textHint:y.textHint}}}function C(s={}){let a={...B,...s,weights:{...B.weights,...s.weights}},u=new WeakMap,d=new WeakMap;return{findMatch:(f,y)=>D(f,y,a,u,d),computeMatches:(f,y)=>$(f,y,a,u,d),explain:(f,y)=>W(f,y,a,u),invalidate:f=>oe(f,u,d),session:()=>{let f=new WeakMap,y=new WeakMap;return{findMatch:(H,b)=>D(H,b,a,f,y),computeMatches:(H,b)=>$(H,b,a,f,y),explain:(H,b)=>W(H,b,a,f)}},getConfig:()=>({...a})}}var ue={createMatcher:C,DEFAULT_CONFIG:B};typeof exports<"u"&&(typeof module<"u"&&module.exports&&(module.exports=ue),exports.createMatcher=C,exports.DEFAULT_CONFIG=B);var le=C(),V=(function(){"use strict";let s=()=>{},a={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:s,afterNodeAdded:s,beforeNodeMorphed:s,afterNodeMorphed:s,beforeNodeRemoved:s,afterNodeRemoved:s,beforeAttributeUpdated:s},head:{style:"merge",shouldPreserve:c=>c.getAttribute("im-preserve")==="true",shouldReAppend:c=>c.getAttribute("im-re-append")==="true",shouldRemove:s,afterHeadMorphed:s},scripts:{handle:!1,shouldPreserve:c=>c.getAttribute("im-preserve")==="true",shouldReAppend:c=>c.getAttribute("im-re-append")==="true",shouldRemove:s,afterScriptsHandled:s},restoreFocus:!0},u={computeMatches(c,S){let{computeMatches:p}=le.session();return p(c,S)}};function d(c,S,p={}){c=E(c);let M=x(S),v=I(c,M,p),m=new Set(Array.from(c.querySelectorAll("script")).map(e=>e.outerHTML)),n=y(v,()=>k(v,c,M,e=>e.morphStyle==="innerHTML"?(H(e,c,M),Array.from(c.childNodes)):f(e,c,M)));v.pantry.remove();let o=N(c,m,v);return o.length>0?n instanceof Promise?n.then(e=>Promise.all(o).then(()=>e)):Promise.all(o).then(()=>n):n}function f(c,S,p){let M=x(S);return H(c,M,p,S,S.nextSibling),Array.from(M.childNodes)}function y(c,S){if(!c.config.restoreFocus)return S();let p=document.activeElement;if(!(p instanceof HTMLInputElement||p instanceof HTMLTextAreaElement))return S();let{id:M,selectionStart:v,selectionEnd:m}=p,n=S();return M&&M!==document.activeElement?.getAttribute("id")&&(p=c.target.querySelector(`[id="${M}"]`),p?.focus()),p&&!p.selectionEnd&&m!=null&&p.setSelectionRange(v,m),n}let H=(function(){function c(e,t,i,r=null,l=null){t instanceof HTMLTemplateElement&&i instanceof HTMLTemplateElement&&(t=t.content,i=i.content),r||=t.firstChild;for(let h of i.childNodes){if(r&&r!=l){let A=p(e,h,r,l);if(A){A!==r&&v(e,r,A),b(A,h,e),r=A.nextSibling;continue}}if(h instanceof Element){let A=h.getAttribute("id");if(e.persistentIds.has(A)){let T=m(t,A,r,e);b(T,h,e),r=T.nextSibling;continue}if(!e.idMap.has(h)){let T=e.hyperMatches.get(h);if(T&&!e.idMap.has(T)){o(t,T,r),b(T,h,e),r=T.nextSibling;continue}}}let g=S(t,h,r,e);g&&(r=g.nextSibling)}for(;r&&r!=l;){let h=r;r=r.nextSibling,M(e,h)}}function S(e,t,i,r){if(r.callbacks.beforeNodeAdded(t)===!1)return null;if(r.idMap.has(t)){let l=document.createElement(t.tagName);return e.insertBefore(l,i),b(l,t,r),r.callbacks.afterNodeAdded(l),l}else{let l=document.importNode(t,!0);return e.insertBefore(l,i),r.callbacks.afterNodeAdded(l),l}}let p=(function(){function e(r,l,h,g){let A=l instanceof Element&&!r.idMap.has(l)?r.hyperMatches.get(l):null,T=null,O=l.nextSibling,j=0,w=h;for(;w&&w!=g;){if(i(w,l)){if(t(r,w,l)||w===A&&!r.idMap.has(w))return w;if(T===null){let _=w instanceof Element&&r.hyperMatchedOldElements.has(w);!r.idMap.has(w)&&!_&&(T=w)}}if(T===null&&O&&i(w,O)&&(j++,O=O.nextSibling,j>=2&&(T=void 0)),r.activeElementAndParents.includes(w))break;w=w.nextSibling}return T||null}function t(r,l,h){let g=r.idMap.get(l),A=r.idMap.get(h);if(!A||!g)return!1;for(let T of g)if(A.has(T))return!0;return!1}function i(r,l){let h=r,g=l;return h.nodeType===g.nodeType&&h.tagName===g.tagName&&(!h.getAttribute?.("id")||h.getAttribute?.("id")===g.getAttribute?.("id"))}return e})();function M(e,t){let i=t instanceof Element&&e.hyperMatchedOldElements.has(t)&&!e.idMap.has(t);if(e.idMap.has(t)||i)o(e.pantry,t,null);else{if(e.callbacks.beforeNodeRemoved(t)===!1)return;t.parentNode?.removeChild(t),e.callbacks.afterNodeRemoved(t)}}function v(e,t,i){let r=t;for(;r&&r!==i;){let l=r;r=r.nextSibling,M(e,l)}return r}function m(e,t,i,r){let l=r.target.getAttribute?.("id")===t&&r.target||r.target.querySelector(`[id="${t}"]`)||r.pantry.querySelector(`[id="${t}"]`);return n(l,r),o(e,l,i),l}function n(e,t){let i=e.getAttribute("id");for(;e=e.parentNode;){let r=t.idMap.get(e);r&&(r.delete(i),r.size||t.idMap.delete(e))}}function o(e,t,i){if(e.moveBefore)try{e.moveBefore(t,i)}catch{e.insertBefore(t,i)}else e.insertBefore(t,i)}return c})(),b=(function(){function c(n,o,e){return e.ignoreActive&&n===document.activeElement?null:(e.callbacks.beforeNodeMorphed(n,o)===!1||(n instanceof HTMLHeadElement&&e.head.ignore||(n instanceof HTMLHeadElement&&e.head.style!=="morph"?L(n,o,e):(S(n,o,e),m(n,e)||H(e,n,o))),e.callbacks.afterNodeMorphed(n,o)),n)}function S(n,o,e){let t=o.nodeType;if(t===1){let i=n,r=o,l=i.attributes,h=r.attributes;for(let g of h)v(g.name,i,"update",e)||i.getAttribute(g.name)!==g.value&&i.setAttribute(g.name,g.value);for(let g=l.length-1;0<=g;g--){let A=l[g];if(A&&!r.hasAttribute(A.name)){if(v(A.name,i,"remove",e))continue;i.removeAttribute(A.name)}}m(i,e)||p(i,r,e)}(t===8||t===3)&&n.nodeValue!==o.nodeValue&&(n.nodeValue=o.nodeValue)}function p(n,o,e){if(n instanceof HTMLInputElement&&o instanceof HTMLInputElement&&o.type!=="file"){let t=o.value,i=n.value;M(n,o,"checked",e),M(n,o,"disabled",e),o.hasAttribute("value")?i!==t&&(v("value",n,"update",e)||(n.setAttribute("value",t),n.value=t)):v("value",n,"remove",e)||(n.value="",n.removeAttribute("value"))}else if(n instanceof HTMLOptionElement&&o instanceof HTMLOptionElement)M(n,o,"selected",e);else if(n instanceof HTMLTextAreaElement&&o instanceof HTMLTextAreaElement){let t=o.value,i=n.value;if(v("value",n,"update",e))return;t!==i&&(n.value=t),n.firstChild&&n.firstChild.nodeValue!==t&&(n.firstChild.nodeValue=t)}}function M(n,o,e,t){let i=o[e],r=n[e];if(i!==r){let l=v(e,n,"update",t);l||(n[e]=o[e]),i?l||n.setAttribute(e,""):v(e,n,"remove",t)||n.removeAttribute(e)}}function v(n,o,e,t){return n==="value"&&t.ignoreActiveValue&&o===document.activeElement?!0:t.callbacks.beforeAttributeUpdated(n,o,e)===!1}function m(n,o){return!!o.ignoreActiveValue&&n===document.activeElement&&n!==document.body}return c})();function k(c,S,p,M){if(c.head.block){let v=S.querySelector("head"),m=p.querySelector("head");if(v&&m){let n=L(v,m,c);return Promise.all(n).then(()=>{let o=Object.assign(c,{head:{block:!1,ignore:!0}});return M(o)})}}return M(c)}function L(c,S,p){let M=[],v=[],m=[],n=[],o=new Map;for(let t of S.children)o.set(t.outerHTML,t);for(let t of c.children){let i=o.has(t.outerHTML),r=p.head.shouldReAppend(t),l=p.head.shouldPreserve(t);i||l?r?v.push(t):(o.delete(t.outerHTML),m.push(t)):p.head.style==="append"?r&&(v.push(t),n.push(t)):p.head.shouldRemove(t)!==!1&&v.push(t)}n.push(...o.values());let e=[];for(let t of n){let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(p.callbacks.beforeNodeAdded(i)!==!1){if("href"in i&&i.href||"src"in i&&i.src){let r,l=new Promise(function(h){r=h});i.addEventListener("load",function(){r()}),e.push(l)}c.appendChild(i),p.callbacks.afterNodeAdded(i),M.push(i)}}for(let t of v)p.callbacks.beforeNodeRemoved(t)!==!1&&(c.removeChild(t),p.callbacks.afterNodeRemoved(t));return p.head.afterHeadMorphed(c,{added:M,kept:m,removed:v}),e}function N(c,S,p){if(!p.scripts.handle)return[];let M=[],v=[],m=[],n=[],o=Array.from(c.querySelectorAll("script"));for(let t of o){let i=t.outerHTML,r=S.has(i),l=p.scripts.shouldPreserve(t),h=p.scripts.shouldReAppend(t);r||l?h?(v.push(t),n.push(t)):m.push(t):n.push(t)}for(let t of S){let i=o.some(r=>r.outerHTML===t)}let e=[];for(let t of n){if(p.callbacks.beforeNodeAdded(t)===!1)continue;let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(i.src){let r,l=new Promise(function(h){r=h});i.addEventListener("load",function(){r()}),i.addEventListener("error",function(){r()}),e.push(l)}t.replaceWith(i),p.callbacks.afterNodeAdded(i),M.push(i)}return p.scripts.afterScriptsHandled(c,{added:M,kept:m,removed:v}),e}let I=(function(){function c(e,t,i){let{persistentIds:r,idMap:l}=n(e,t),h=u.computeMatches(e,t),g=new Set;for(let O of h.values())g.add(O);let A=S(i),T=A.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(T))throw`Do not understand how to morph style ${T}`;return{target:e,newContent:t,config:A,morphStyle:T,ignoreActive:A.ignoreActive,ignoreActiveValue:A.ignoreActiveValue,restoreFocus:A.restoreFocus,idMap:l,persistentIds:r,hyperMatches:h,hyperMatchedOldElements:g,pantry:p(),activeElementAndParents:M(e),callbacks:A.callbacks,head:A.head,scripts:A.scripts}}function S(e){let t=Object.assign({},a);return Object.assign(t,e),t.callbacks=Object.assign({},a.callbacks,e.callbacks),t.head=Object.assign({},a.head,e.head),t.scripts=Object.assign({},a.scripts,e.scripts),t}function p(){let e=document.createElement("div");return e.hidden=!0,document.body.insertAdjacentElement("afterend",e),e}function M(e){let t=[],i=document.activeElement;if(i?.tagName!=="BODY"&&e.contains(i))for(;i&&(t.push(i),i!==e);)i=i.parentElement;return t}function v(e){let t=Array.from(e.querySelectorAll("[id]"));return e.getAttribute?.("id")&&t.push(e),t}function m(e,t,i,r){for(let l of r){let h=l.getAttribute("id");if(t.has(h)){let g=l;for(;g;){let A=e.get(g);if(A==null&&(A=new Set,e.set(g,A)),A.add(h),g===i)break;g=g.parentElement}}}}function n(e,t){let i=v(e),r=v(t),l=o(i,r),h=new Map;m(h,l,e,i);let g=t.__hyperMorphRoot||t;return m(h,l,g,r),{persistentIds:l,idMap:h}}function o(e,t){let i=new Set,r=new Map;for(let{id:h,tagName:g}of e)r.has(h)?i.add(h):r.set(h,g);let l=new Set;for(let{id:h,tagName:g}of t)l.has(h)?i.add(h):r.get(h)===g&&l.add(h);for(let h of i)l.delete(h);return l}return c})(),{normalizeElement:E,normalizeParent:x}=(function(){let c=new WeakSet;function S(m){return m instanceof Document?m.documentElement:m}function p(m){if(m==null)return document.createElement("div");if(typeof m=="string")return p(v(m));if(c.has(m))return m;if(m instanceof Node){if(m.parentNode)return new M(m);{let n=document.createElement("div");return n.append(m),n}}else{let n=document.createElement("div");for(let o of[...m])n.append(o);return n}}class M{constructor(n){this.originalNode=n,this.realParentNode=n.parentNode,this.previousSibling=n.previousSibling,this.nextSibling=n.nextSibling}get childNodes(){let n=[],o=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;for(;o&&o!=this.nextSibling;)n.push(o),o=o.nextSibling;return n}querySelectorAll(n){return this.childNodes.reduce((o,e)=>{if(e instanceof Element){e.matches(n)&&o.push(e);let t=e.querySelectorAll(n);for(let i=0;i<t.length;i++)o.push(t[i])}return o},[])}insertBefore(n,o){return this.realParentNode.insertBefore(n,o)}moveBefore(n,o){return this.realParentNode.moveBefore(n,o)}get __hyperMorphRoot(){return this.originalNode}}function v(m){let n=new DOMParser,o=m.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(o.match(/<\/html>/)||o.match(/<\/head>/)||o.match(/<\/body>/)){let e=n.parseFromString(m,"text/html");if(o.match(/<\/html>/))return c.add(e),e;{let t=e.firstChild;return t&&c.add(t),t}}else{let t=n.parseFromString("<body><template>"+m+"</template></body>","text/html").body.querySelector("template").content;return c.add(t),t}}return{normalizeElement:S,normalizeParent:p}})();return{morph:d,defaults:a}})();var ce=V.morph,de=V.defaults,fe=V;return K(he);})();
|
|
1
|
+
var HyperMorph=(()=>{var V=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var J=(s,o)=>{for(var u in o)V(s,u,{get:o[u],enumerable:!0})},Q=(s,o,u,d)=>{if(o&&typeof o=="object"||typeof o=="function")for(let h of G(o))!Y.call(s,h)&&h!==u&&V(s,h,{get:()=>o[h],enumerable:!(d=K(o,h))||d.enumerable});return s};var X=s=>Q(V({},"__esModule",{value:!0}),s);var pe={};J(pe,{HyperMorph:()=>$,default:()=>he,defaults:()=>fe,morph:()=>de});var F={includeClasses:!0,includeAttributes:["href","src","name","type","role","aria-label","alt","title"],excludeAttributePrefixes:["data-morph-","data-hyper-","data-im-"],textHintLength:64,excludeIds:!0,maxPathDepth:4,landmarks:["HEADER","NAV","MAIN","ASIDE","FOOTER","SECTION","ARTICLE"],weights:{signature:100,pathSegment:10,textMatch:20,textMismatch:25,uniqueCandidate:50,positionPenalty:1},minConfidence:101};function Z(s){let o=5381;for(let u=0;u<s.length;u++)o=(o<<5)+o^s.charCodeAt(u);return Math.abs(o).toString(36)}function ee(s){if(s.classList&&s.classList.length>0)return Array.from(s.classList).sort().join(" ");let o=s.getAttribute?.("class");return o?o.split(/\s+/).filter(Boolean).sort().join(" "):""}function te(s,o){let u=[];for(let d of s.attributes||[]){let h=d.name;h==="id"||h==="class"||o.excludeAttributePrefixes.some(y=>h.startsWith(y))||o.includeAttributes.includes(h)&&u.push(`${h}=${d.value}`)}return u.sort().join("|")}function ne(s,o){return(s.textContent||"").replace(/\s+/g," ").trim().slice(0,o.textHintLength)}function re(s,o){let u=[s.tagName];return o.includeClasses&&u.push(ee(s)),u.push(te(s,o)),Z(u.join("|"))}function se(s){let o=s.tagName,u=1,d=s.previousElementSibling;for(;d;)d.tagName===o&&u++,d=d.previousElementSibling;return u}function ie(s,o){return s.id||s.getAttribute?.("role")?!0:o.landmarks.includes(s.tagName)}function ae(s){if(s.id)return`#${s.id}`;let o=s.getAttribute?.("role");return o?`@${o}`:s.tagName}function oe(s,o){let u=[],d=s;for(;d&&d.tagName&&u.length<o.maxPathDepth;){let h=`${d.tagName}:${se(d)}`;if(u.unshift(h),d!==s&&ie(d,o)){u.unshift(ae(d));break}d=d.parentElement}return u}function ue(s,o){let u=0,d=s.length-1,h=o.length-1;for(;d>=0&&h>=0&&s[d]===o[h];)u++,d--,h--;return u}function C(s,o,u){if(u.has(s))return u.get(s);let d={signature:re(s,o),path:oe(s,o),textHint:ne(s,o)};return u.set(s,d),d}function z(s,o,u,d){if(d.has(s))return d.get(s);let h=new Map,y=s.querySelectorAll("*"),H=0;for(let S of y){let I=C(S,o,u);I.domIndex=H++,h.has(I.signature)||h.set(I.signature,[]),h.get(I.signature).push(S)}return d.set(s,h),h}function ce(s,o,u){u.delete(s),o.delete(s);let d=s.querySelectorAll("*");for(let h of d)o.delete(h)}function q(s,o,u,d,h){let y=C(s,u,d),H=C(o,u,d),S=u.weights,I={},E=0;if(y.signature!==H.signature)return{score:0,breakdown:{rejected:"signature mismatch"}};E+=S.signature,I.signature=S.signature;let T=ue(y.path,H.path)*S.pathSegment;E+=T,I.path=T;let k=!0;if(y.textHint&&H.textHint?y.textHint===H.textHint?(E+=S.textMatch,I.text=S.textMatch):(E-=S.textMismatch,I.text=-S.textMismatch,k=!1):y.textHint!==H.textHint&&(E-=S.textMismatch,I.text=-S.textMismatch,k=!1),h.candidateCount===1&&k&&(E+=S.uniqueCandidate,I.unique=S.uniqueCandidate),typeof y.domIndex=="number"&&typeof H.domIndex=="number"){let N=Math.abs(y.domIndex-H.domIndex),R=Math.min(N*S.positionPenalty,20);E-=R,I.drift=-R}return{score:E,breakdown:I}}function D(s,o,u,d,h){if(u.excludeIds&&s.id)return null;let y=z(o,u,d,h),H=C(s,u,d);if(typeof H.domIndex!="number"){let k=0,N=s.previousElementSibling;for(;N;)k++,N=N.previousElementSibling;H.domIndex=k}let S=y.get(H.signature)||[],I=u.excludeIds?S.filter(k=>!k.id):S;if(I.length===0)return null;let E=null,x=0,T=null;for(let k of I){let{score:N,breakdown:R}=q(s,k,u,d,{candidateCount:I.length});N>x&&(x=N,E=k,T=R)}return x<u.minConfidence?null:{element:E,confidence:x,breakdown:T}}function W(s,o,u,d,h){let y=o.querySelectorAll("*"),H=z(s,u,d,h),S=0;for(let T of y){let k=C(T,u,d);k.domIndex=S++}let I=[];for(let T of y){if(u.excludeIds&&T.id)continue;let k=C(T,u,d),N=H.get(k.signature)||[],R=u.excludeIds?N.filter(B=>!B.id):N;for(let B of R){let{score:l,breakdown:A}=q(T,B,u,d,{candidateCount:R.length});l>=u.minConfidence&&I.push({newEl:T,oldEl:B,score:l,breakdown:A})}}I.sort((T,k)=>k.score-T.score);let E=new Map,x=new Set;for(let{newEl:T,oldEl:k}of I)E.has(T)||x.has(k)||(E.set(T,k),x.add(k));return E}function P(s,o,u,d){let h=C(s,u,d),y=C(o,u,d),{score:H,breakdown:S}=q(s,o,u,d,{candidateCount:1});return{matches:H>=u.minConfidence,score:H,breakdown:S,newMeta:{signature:h.signature,path:h.path,textHint:h.textHint},oldMeta:{signature:y.signature,path:y.path,textHint:y.textHint}}}function U(s={}){let o={...F,...s,weights:{...F.weights,...s.weights}},u=new WeakMap,d=new WeakMap;return{findMatch:(h,y)=>D(h,y,o,u,d),computeMatches:(h,y)=>W(h,y,o,u,d),explain:(h,y)=>P(h,y,o,u),invalidate:h=>ce(h,u,d),session:()=>{let h=new WeakMap,y=new WeakMap;return{findMatch:(H,S)=>D(H,S,o,h,y),computeMatches:(H,S)=>W(H,S,o,h,y),explain:(H,S)=>P(H,S,o,h)}},getConfig:()=>({...o})}}var le=U(),$=(function(){"use strict";let s=()=>{};function o(l){if(!(l instanceof Element))return!1;if(l.hasAttribute("save-ignore"))return!0;if(l.tagName==="LINK"||l.tagName==="SCRIPT"){let A=l.getAttribute("src")||l.getAttribute("href")||"";if(A.startsWith("chrome-extension://")||A.startsWith("moz-extension://")||A.startsWith("safari-web-extension://"))return!0}return!1}function u(l,A){if(A!=="smart")return l.outerHTML;let p=l.getAttribute("src"),v=l.getAttribute("type")||"text/javascript";if(p)try{let b=new URL(p,window.location.href);return`ext:${v}:${b.origin}${b.pathname}`}catch{return`ext:${v}:${p}`}else{let b=l.textContent.trim(),g=5381;for(let r=0;r<b.length;r++)g=(g<<5)+g^b.charCodeAt(r);return`inline:${v}:${Math.abs(g).toString(36)}`}}let d={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:s,afterNodeAdded:s,beforeNodeMorphed:s,afterNodeMorphed:s,beforeNodeRemoved:s,afterNodeRemoved:s,beforeAttributeUpdated:s},head:{style:"merge",shouldPreserve:l=>l.getAttribute("im-preserve")==="true",shouldReAppend:l=>l.getAttribute("im-re-append")==="true",shouldRemove:s,afterHeadMorphed:s},scripts:{handle:!1,matchMode:"outerHTML",shouldPreserve:l=>l.getAttribute("im-preserve")==="true",shouldReAppend:l=>l.getAttribute("im-re-append")==="true",shouldRemove:s,afterScriptsHandled:s},restoreFocus:!0},h={computeMatches(l,A){let{computeMatches:p}=le.session();return p(l,A)}};function y(l,A,p={}){l=R(l);let v=B(A),b=N(l,v,p),g=b.scripts.matchMode,r=new Set(Array.from(l.querySelectorAll("script")).map(n=>u(n,g))),c=S(b,()=>x(b,l,v,n=>n.morphStyle==="innerHTML"?(I(n,l,v),Array.from(l.childNodes)):H(n,l,v)));b.pantry.remove();let e=k(l,r,b);return e.length>0?c instanceof Promise?c.then(n=>Promise.all(e).then(()=>n)):Promise.all(e).then(()=>c):c}function H(l,A,p){let v=B(A);return I(l,v,p,A,A.nextSibling),Array.from(v.childNodes)}function S(l,A){if(!l.config.restoreFocus)return A();let p=document.activeElement;if(!(p instanceof HTMLInputElement||p instanceof HTMLTextAreaElement))return A();let{id:v,selectionStart:b,selectionEnd:g}=p,r=A();return v&&v!==document.activeElement?.getAttribute("id")&&(p=l.target.querySelector(`[id="${v}"]`),p?.focus()),p&&!p.selectionEnd&&g!=null&&p.setSelectionRange(b,g),r}let I=(function(){function l(e,n,i,t=null,a=null){n instanceof HTMLTemplateElement&&i instanceof HTMLTemplateElement&&(n=n.content,i=i.content),t||=n.firstChild;for(let f of i.childNodes){if(o(f))continue;if(t&&t!=a){let M=p(e,f,t,a);if(M){M!==t&&b(e,t,M),E(M,f,e),t=M.nextSibling;continue}}if(f instanceof Element){let M=f.getAttribute("id");if(e.persistentIds.has(M)){let w=g(n,M,t,e);E(w,f,e),t=w.nextSibling;continue}if(!e.idMap.has(f)){let w=e.hyperMatches.get(f);if(w&&!e.idMap.has(w)){c(n,w,t),E(w,f,e),t=w.nextSibling;continue}}}let m=A(n,f,t,e);m&&(t=m.nextSibling)}for(;t&&t!=a;){let f=t;t=t.nextSibling,o(f)||v(e,f)}}function A(e,n,i,t){if(t.callbacks.beforeNodeAdded(n)===!1)return null;if(t.idMap.has(n)){let a=document.createElement(n.tagName);return e.insertBefore(a,i),E(a,n,t),t.callbacks.afterNodeAdded(a),a}else{let a=document.importNode(n,!0);return e.insertBefore(a,i),t.callbacks.afterNodeAdded(a),a}}let p=(function(){function e(t,a,f,m){let M=a instanceof Element&&!t.idMap.has(a)?t.hyperMatches.get(a):null,w=null,O=a.nextSibling,j=0,L=f;for(;L&&L!=m;){if(i(L,a)){if(n(t,L,a)||L===M&&!t.idMap.has(L))return L;if(w===null){let _=L instanceof Element&&t.hyperMatchedOldElements.has(L);!t.idMap.has(L)&&!_&&(w=L)}}if(w===null&&O&&i(L,O)&&(j++,O=O.nextSibling,j>=2&&(w=void 0)),t.activeElementAndParents.includes(L))break;L=L.nextSibling}return w||null}function n(t,a,f){let m=t.idMap.get(a),M=t.idMap.get(f);if(!M||!m)return!1;for(let w of m)if(M.has(w))return!0;return!1}function i(t,a){let f=t,m=a;return f.nodeType===m.nodeType&&f.tagName===m.tagName&&(!f.getAttribute?.("id")||f.getAttribute?.("id")===m.getAttribute?.("id"))}return e})();function v(e,n){let i=n instanceof Element&&e.hyperMatchedOldElements.has(n)&&!e.idMap.has(n);if(e.idMap.has(n)||i)c(e.pantry,n,null);else{if(e.callbacks.beforeNodeRemoved(n)===!1)return;n.parentNode?.removeChild(n),e.callbacks.afterNodeRemoved(n)}}function b(e,n,i){let t=n;for(;t&&t!==i;){let a=t;t=t.nextSibling,o(a)||v(e,a)}return t}function g(e,n,i,t){let a=t.target.getAttribute?.("id")===n&&t.target||t.target.querySelector(`[id="${n}"]`)||t.pantry.querySelector(`[id="${n}"]`);return r(a,t),c(e,a,i),a}function r(e,n){let i=e.getAttribute("id");for(;e=e.parentNode;){let t=n.idMap.get(e);t&&(t.delete(i),t.size||n.idMap.delete(e))}}function c(e,n,i){if(e.moveBefore)try{e.moveBefore(n,i)}catch{e.insertBefore(n,i)}else e.insertBefore(n,i)}return l})(),E=(function(){function l(r,c,e){return e.ignoreActive&&r===document.activeElement?null:(e.callbacks.beforeNodeMorphed(r,c)===!1||(r instanceof HTMLHeadElement&&e.head.ignore||(r instanceof HTMLHeadElement&&e.head.style!=="morph"?T(r,c,e):(A(r,c,e),g(r,e)||I(e,r,c))),e.callbacks.afterNodeMorphed(r,c)),r)}function A(r,c,e){let n=c.nodeType;if(n===1){let i=r,t=c,a=i.attributes,f=t.attributes;for(let m of f)b(m.name,i,"update",e)||i.getAttribute(m.name)!==m.value&&i.setAttribute(m.name,m.value);for(let m=a.length-1;0<=m;m--){let M=a[m];if(M&&!t.hasAttribute(M.name)){if(b(M.name,i,"remove",e))continue;i.removeAttribute(M.name)}}g(i,e)||p(i,t,e)}(n===8||n===3)&&r.nodeValue!==c.nodeValue&&(r.nodeValue=c.nodeValue)}function p(r,c,e){if(r instanceof HTMLInputElement&&c instanceof HTMLInputElement&&c.type!=="file"){let n=c.value,i=r.value;v(r,c,"checked",e),v(r,c,"disabled",e),c.hasAttribute("value")?i!==n&&(b("value",r,"update",e)||(r.setAttribute("value",n),r.value=n)):b("value",r,"remove",e)||(r.value="",r.removeAttribute("value"))}else if(r instanceof HTMLOptionElement&&c instanceof HTMLOptionElement)v(r,c,"selected",e);else if(r instanceof HTMLTextAreaElement&&c instanceof HTMLTextAreaElement){let n=c.value,i=r.value;if(b("value",r,"update",e))return;n!==i&&(r.value=n),r.firstChild&&r.firstChild.nodeValue!==n&&(r.firstChild.nodeValue=n)}}function v(r,c,e,n){let i=c[e],t=r[e];if(i!==t){let a=b(e,r,"update",n);a||(r[e]=c[e]),i?a||r.setAttribute(e,""):b(e,r,"remove",n)||r.removeAttribute(e)}}function b(r,c,e,n){return r==="value"&&n.ignoreActiveValue&&c===document.activeElement?!0:n.callbacks.beforeAttributeUpdated(r,c,e)===!1}function g(r,c){return!!c.ignoreActiveValue&&r===document.activeElement&&r!==document.body}return l})();function x(l,A,p,v){if(l.head.block){let b=A.querySelector("head"),g=p.querySelector("head");if(b&&g){let r=T(b,g,l);return Promise.all(r).then(()=>{let c=Object.assign(l,{head:{block:!1,ignore:!0}});return v(c)})}}return v(l)}function T(l,A,p){let v=[],b=[],g=[],r=[],c=p.scripts.matchMode,e=t=>{if(t.tagName==="SCRIPT")return u(t,c);if(t.tagName==="LINK"&&c==="smart"){let a=t.getAttribute("href");if(a)try{let f=new URL(a,window.location.href);return`link:${t.getAttribute("rel")||""}:${f.origin}${f.pathname}`}catch{}}return t.outerHTML},n=new Map;for(let t of A.children)o(t)||n.set(e(t),t);for(let t of l.children){let a=e(t),f=n.has(a),m=p.head.shouldReAppend(t),M=p.head.shouldPreserve(t);f||M?m?b.push(t):(n.delete(a),g.push(t)):p.head.style==="append"?m&&(b.push(t),r.push(t)):p.head.shouldRemove(t)!==!1&&!o(t)&&b.push(t)}r.push(...n.values());let i=[];for(let t of r){let a=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(p.callbacks.beforeNodeAdded(a)!==!1){if("href"in a&&a.href||"src"in a&&a.src){let f,m=new Promise(function(M){f=M});a.addEventListener("load",function(){f()}),i.push(m)}l.appendChild(a),p.callbacks.afterNodeAdded(a),v.push(a)}}for(let t of b)p.callbacks.beforeNodeRemoved(t)!==!1&&(l.removeChild(t),p.callbacks.afterNodeRemoved(t));return p.head.afterHeadMorphed(l,{added:v,kept:g,removed:b}),i}function k(l,A,p){if(!p.scripts.handle)return[];let v=[],b=[],g=[],r=[],c=p.scripts.matchMode,e=Array.from(l.querySelectorAll("script"));for(let i of e){let t=u(i,c),a=A.has(t),f=p.scripts.shouldPreserve(i),m=p.scripts.shouldReAppend(i);a||f?m?(b.push(i),r.push(i)):g.push(i):r.push(i)}for(let i of A){let t=e.some(a=>a.outerHTML===i)}let n=[];for(let i of r){if(p.callbacks.beforeNodeAdded(i)===!1)continue;let t=document.createRange().createContextualFragment(i.outerHTML).firstChild;if(t.src){let a,f=new Promise(function(m){a=m});t.addEventListener("load",function(){a()}),t.addEventListener("error",function(){a()}),n.push(f)}i.replaceWith(t),p.callbacks.afterNodeAdded(t),v.push(t)}return p.scripts.afterScriptsHandled(l,{added:v,kept:g,removed:b}),n}let N=(function(){function l(e,n,i){let{persistentIds:t,idMap:a}=r(e,n),f=h.computeMatches(e,n),m=new Set;for(let O of f.values())m.add(O);let M=A(i),w=M.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(w))throw`Do not understand how to morph style ${w}`;return{target:e,newContent:n,config:M,morphStyle:w,ignoreActive:M.ignoreActive,ignoreActiveValue:M.ignoreActiveValue,restoreFocus:M.restoreFocus,idMap:a,persistentIds:t,hyperMatches:f,hyperMatchedOldElements:m,pantry:p(),activeElementAndParents:v(e),callbacks:M.callbacks,head:M.head,scripts:M.scripts}}function A(e){let n=Object.assign({},d);return Object.assign(n,e),n.callbacks=Object.assign({},d.callbacks,e.callbacks),n.head=Object.assign({},d.head,e.head),n.scripts=Object.assign({},d.scripts,e.scripts),n}function p(){let e=document.createElement("div");return e.hidden=!0,document.body.insertAdjacentElement("afterend",e),e}function v(e){let n=[],i=document.activeElement;if(i?.tagName!=="BODY"&&e.contains(i))for(;i&&(n.push(i),i!==e);)i=i.parentElement;return n}function b(e){let n=Array.from(e.querySelectorAll("[id]"));return e.getAttribute?.("id")&&n.push(e),n}function g(e,n,i,t){for(let a of t){let f=a.getAttribute("id");if(n.has(f)){let m=a;for(;m;){let M=e.get(m);if(M==null&&(M=new Set,e.set(m,M)),M.add(f),m===i)break;m=m.parentElement}}}}function r(e,n){let i=b(e),t=b(n),a=c(i,t),f=new Map;g(f,a,e,i);let m=n.__hyperMorphRoot||n;return g(f,a,m,t),{persistentIds:a,idMap:f}}function c(e,n){let i=new Set,t=new Map;for(let{id:f,tagName:m}of e)t.has(f)?i.add(f):t.set(f,m);let a=new Set;for(let{id:f,tagName:m}of n)a.has(f)?i.add(f):t.get(f)===m&&a.add(f);for(let f of i)a.delete(f);return a}return l})(),{normalizeElement:R,normalizeParent:B}=(function(){let l=new WeakSet;function A(g){return g instanceof Document?g.documentElement:g}function p(g){if(g==null)return document.createElement("div");if(typeof g=="string")return p(b(g));if(l.has(g))return g;if(g instanceof Node){if(g.parentNode)return new v(g);{let r=document.createElement("div");return r.append(g),r}}else{let r=document.createElement("div");for(let c of[...g])r.append(c);return r}}class v{constructor(r){this.originalNode=r,this.realParentNode=r.parentNode,this.previousSibling=r.previousSibling,this.nextSibling=r.nextSibling}get childNodes(){let r=[],c=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;for(;c&&c!=this.nextSibling;)r.push(c),c=c.nextSibling;return r}querySelectorAll(r){return this.childNodes.reduce((c,e)=>{if(e instanceof Element){e.matches(r)&&c.push(e);let n=e.querySelectorAll(r);for(let i=0;i<n.length;i++)c.push(n[i])}return c},[])}insertBefore(r,c){return this.realParentNode.insertBefore(r,c)}moveBefore(r,c){return this.realParentNode.moveBefore(r,c)}get __hyperMorphRoot(){return this.originalNode}}function b(g){let r=new DOMParser,c=g.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(c.match(/<\/html>/)||c.match(/<\/head>/)||c.match(/<\/body>/)){let e=r.parseFromString(g,"text/html");if(c.match(/<\/html>/))return l.add(e),e;{let n=e.firstChild;return n&&l.add(n),n}}else{let n=r.parseFromString("<body><template>"+g+"</template></body>","text/html").body.querySelector("template").content;return l.add(n),n}}return{normalizeElement:A,normalizeParent:p}})();return{morph:y,defaults:d}})();var de=$.morph,fe=$.defaults,he=$;return X(pe);})();
|
|
2
2
|
|
|
3
3
|
// Convenience morph wrapper with data-id support
|
|
4
4
|
var morph = function(oldEl, newEl, options = {}) {
|