hyperclayjs 1.9.0 → 1.10.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 +3 -3
- package/package.json +1 -1
- package/src/communication/live-sync.js +37 -21
- package/src/core/snapshot.js +2 -2
- package/src/hyperclay.js +1 -1
- package/src/module-dependency-graph.json +3 -3
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
63
63
|
| option-visibility | 5.3KB | 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
65
|
| save-core | 6.8KB | Basic save function only - hyperclay.savePage() |
|
|
66
|
-
| save-system | 7.1KB |
|
|
66
|
+
| save-system | 7.1KB | CMD+S, [trigger-save] button, savestatus attribute |
|
|
67
67
|
| save-toast | 0.9KB | Toast notifications for save events |
|
|
68
68
|
| snapshot | 7.5KB | Source of truth for page state - captures DOM snapshots for save and sync |
|
|
69
69
|
| unsaved-warning | 0.8KB | Warn before leaving page with unsaved changes |
|
|
@@ -120,7 +120,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
120
120
|
| Module | Size | Description |
|
|
121
121
|
|--------|------|-------------|
|
|
122
122
|
| file-upload | 10.7KB | File upload with progress |
|
|
123
|
-
| live-sync |
|
|
123
|
+
| live-sync | 12.7KB | Real-time DOM sync across browsers |
|
|
124
124
|
| send-message | 1.3KB | Message sending utility |
|
|
125
125
|
|
|
126
126
|
### Vendor Libraries (Third-party libraries)
|
|
@@ -141,7 +141,7 @@ Standard feature set for most use cases
|
|
|
141
141
|
|
|
142
142
|
**Modules:** `save-core`, `save-system`, `edit-mode-helpers`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`, `save-toast`, `export-to-window`
|
|
143
143
|
|
|
144
|
-
### Everything (~
|
|
144
|
+
### Everything (~177.2KB)
|
|
145
145
|
All available features
|
|
146
146
|
|
|
147
147
|
Includes all available modules across all categories.
|
package/package.json
CHANGED
|
@@ -228,26 +228,34 @@ class LiveSync {
|
|
|
228
228
|
|
|
229
229
|
/**
|
|
230
230
|
* Listen for snapshot-ready events from the save system.
|
|
231
|
-
*
|
|
231
|
+
* Receives the full cloned documentElement and extracts head/body.
|
|
232
232
|
*/
|
|
233
233
|
listenForSnapshots() {
|
|
234
|
-
this._snapshotHandler = (event) => {
|
|
234
|
+
this._snapshotHandler = async (event) => {
|
|
235
235
|
if (this.isPaused) return;
|
|
236
236
|
|
|
237
|
-
const {
|
|
238
|
-
if (!
|
|
237
|
+
const { documentElement } = event.detail;
|
|
238
|
+
if (!documentElement) return;
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
// Extract head and body directly from cloned element
|
|
241
|
+
const head = documentElement.querySelector('head')?.innerHTML || '';
|
|
242
|
+
const body = documentElement.querySelector('body')?.innerHTML || '';
|
|
243
|
+
|
|
244
|
+
// Compute headHash using SHA-256 (async)
|
|
245
|
+
const headHash = await this.computeHeadHash(head);
|
|
246
|
+
|
|
247
|
+
// Send update even if body is empty (allows clearing content)
|
|
248
|
+
this.sendUpdate(body, headHash);
|
|
241
249
|
};
|
|
242
250
|
|
|
243
251
|
document.addEventListener('hyperclay:snapshot-ready', this._snapshotHandler);
|
|
244
252
|
}
|
|
245
253
|
|
|
246
254
|
/**
|
|
247
|
-
* Send body
|
|
255
|
+
* Send body and headHash to the server (debounced)
|
|
248
256
|
* Only updates lastBodyHtml after successful save
|
|
249
257
|
*/
|
|
250
|
-
|
|
258
|
+
sendUpdate(body, headHash) {
|
|
251
259
|
clearTimeout(this.debounceTimer);
|
|
252
260
|
|
|
253
261
|
this.debounceTimer = setTimeout(() => {
|
|
@@ -256,9 +264,8 @@ class LiveSync {
|
|
|
256
264
|
|
|
257
265
|
console.log('[LiveSync] Sending update');
|
|
258
266
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
this.lastHeadHash = headHash; // Track local head changes
|
|
267
|
+
// Track local head changes
|
|
268
|
+
this.lastHeadHash = headHash;
|
|
262
269
|
|
|
263
270
|
fetch('/live-sync/save', {
|
|
264
271
|
method: 'POST',
|
|
@@ -286,21 +293,30 @@ class LiveSync {
|
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
/**
|
|
289
|
-
* Compute
|
|
290
|
-
* Uses
|
|
296
|
+
* Compute SHA-256 hash of head content (first 16 hex chars)
|
|
297
|
+
* Uses SubtleCrypto API (async)
|
|
298
|
+
* @param {string} head - Head innerHTML
|
|
299
|
+
* @returns {Promise<string|null>} 16-char hex hash or null if unavailable
|
|
291
300
|
*/
|
|
292
|
-
computeHeadHash() {
|
|
293
|
-
const head = document.head?.innerHTML;
|
|
301
|
+
async computeHeadHash(head) {
|
|
294
302
|
if (!head) return null;
|
|
295
303
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
304
|
+
// SubtleCrypto requires secure context (HTTPS or localhost)
|
|
305
|
+
if (!crypto?.subtle?.digest) {
|
|
306
|
+
console.warn('[LiveSync] SHA-256 unavailable (non-secure context), skipping headHash');
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const encoder = new TextEncoder();
|
|
312
|
+
const data = encoder.encode(head);
|
|
313
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
314
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
315
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16);
|
|
316
|
+
} catch (e) {
|
|
317
|
+
console.warn('[LiveSync] SHA-256 hash failed, skipping headHash:', e);
|
|
318
|
+
return null;
|
|
301
319
|
}
|
|
302
|
-
// Convert to hex and take first 8 chars
|
|
303
|
-
return Math.abs(hash).toString(16).padStart(8, '0').slice(0, 8);
|
|
304
320
|
}
|
|
305
321
|
|
|
306
322
|
/**
|
package/src/core/snapshot.js
CHANGED
|
@@ -128,10 +128,10 @@ export function captureForSave({ emitForSync = true } = {}) {
|
|
|
128
128
|
const clone = captureSnapshot();
|
|
129
129
|
|
|
130
130
|
// Emit for live-sync before stripping admin elements
|
|
131
|
+
// Sends full cloned documentElement so live-sync can extract head and body
|
|
131
132
|
if (emitForSync) {
|
|
132
|
-
const bodyForSync = clone.querySelector('body').innerHTML;
|
|
133
133
|
document.dispatchEvent(new CustomEvent('hyperclay:snapshot-ready', {
|
|
134
|
-
detail: {
|
|
134
|
+
detail: { documentElement: clone }
|
|
135
135
|
}));
|
|
136
136
|
}
|
|
137
137
|
|
package/src/hyperclay.js
CHANGED
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
"files": [
|
|
179
179
|
"core/savePage.js"
|
|
180
180
|
],
|
|
181
|
-
"description": "
|
|
181
|
+
"description": "CMD+S, [trigger-save] button, savestatus attribute",
|
|
182
182
|
"exports": {
|
|
183
183
|
"beforeSave": [
|
|
184
184
|
"hyperclay"
|
|
@@ -724,11 +724,11 @@
|
|
|
724
724
|
"live-sync": {
|
|
725
725
|
"name": "live-sync",
|
|
726
726
|
"category": "communication",
|
|
727
|
-
"size": 12,
|
|
727
|
+
"size": 12.7,
|
|
728
728
|
"files": [
|
|
729
729
|
"communication/live-sync.js"
|
|
730
730
|
],
|
|
731
|
-
"description": "Real-time DOM sync across browsers
|
|
731
|
+
"description": "Real-time DOM sync across browsers",
|
|
732
732
|
"exports": {
|
|
733
733
|
"liveSync": [
|
|
734
734
|
"hyperclay"
|