@panphora/clayjs 1.2.0 → 1.4.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 +7 -3
- package/THIRD-PARTY-NOTICES.md +10 -0
- package/dist/clay.standalone.js +22539 -14260
- package/entries/clay-data.js +1 -1
- package/entries/sap.js +1 -1
- package/package.json +7 -2
- package/packed-contract.json +17 -0
- package/src/attrs/save-freeze.js +10 -18
- package/src/core/admin-contenteditable.js +8 -6
- package/src/core/admin-inputs.js +23 -13
- package/src/core/admin-onclick.js +5 -0
- package/src/core/is-edit-mode.js +7 -2
- package/src/core/persist.js +5 -10
- package/src/core/save-core.js +35 -0
- package/src/core/save.js +13 -1
- package/src/core/snapshot.js +186 -14
- package/src/core/source-map.js +1025 -0
- package/src/core/unsaved-warning.js +3 -0
- package/src/dom/dom-helpers.js +5 -1
- package/src/lib/content-dom.js +108 -0
- package/src/lib/mutation.js +26 -3
- package/src/lib/region-capabilities.js +69 -0
- package/src/lib/region-policy.js +18 -13
- package/src/loader-logic.js +27 -5
- package/src/loader.js +6 -0
- package/src/plugins/ai-edit.js +625 -0
- package/src/plugins/demo.js +3 -0
- package/src/plugins/sortable.js +6 -1
- package/src/plugins/source.js +410 -0
- package/src/plugins/wire.js +248 -47
- package/src/sync/live-sync.js +106 -42
- package/src/sync/presence.js +303 -0
- package/src/sync/section-notice.js +230 -0
- package/src/sync/splice-merge.js +7 -10
- package/src/sync/stream.js +190 -0
- package/src/vendor/control-serialize.vendor.js +10 -6
- package/src/vendor/hyper-morph.vendor.js +2 -2
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +438 -45
- package/src/vendor/parse5.vendor.js +3 -0
- package/src/vendor/quickcrop.vendor.js +1 -1
- package/src/vendor/richclay.vendor.js +22 -15
package/src/core/save.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
import { captureForComparison, captureForComparisonAndDirty, captureForSaveAndComparison } from "./snapshot.js";
|
|
24
24
|
import { seedEtag } from "./etag.js";
|
|
25
25
|
import { gateCaptureToken, gateClearIfUnchanged } from "../lib/dirty-gate.js";
|
|
26
|
-
import { ROOT_LIBRARY_ATTRS } from "../lib/root-attrs.js";
|
|
26
|
+
import { ROOT_LIBRARY_ATTRS, SAVE_TOKEN_ATTRS, LEGACY_SAVE_TOKEN_ATTRS } from "../lib/root-attrs.js";
|
|
27
27
|
import { logSaveCheck, logBaseline } from "../lib/autosave-debug.js";
|
|
28
28
|
import { initUserGesture, markExplicitSave, clearExplicitSave } from "../lib/user-gesture.js";
|
|
29
29
|
|
|
@@ -43,6 +43,18 @@ addDocumentTransform(clone => {
|
|
|
43
43
|
for (const name of ROOT_LIBRARY_ATTRS) clone.removeAttribute(name);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
// Keep the host's save token out of the saved bytes, both spellings.
|
|
47
|
+
//
|
|
48
|
+
// It is a credential for this response, never file content: htmlclay strips it from
|
|
49
|
+
// every save body on arrival, so it never reached disk anyway. Sending it made the
|
|
50
|
+
// source map, which models the bytes a save sent, describe a root tag one attribute
|
|
51
|
+
// longer than the file, and every offset after it was off by that much. The save
|
|
52
|
+
// itself is authorized by the URL, which reads the token from the live page. The
|
|
53
|
+
// document id is NOT stripped: htmlclay keeps it on disk on purpose.
|
|
54
|
+
addDocumentTransform(clone => {
|
|
55
|
+
for (const name of [...SAVE_TOKEN_ATTRS, ...LEGACY_SAVE_TOKEN_ATTRS]) clone.removeAttribute(name);
|
|
56
|
+
});
|
|
57
|
+
|
|
46
58
|
// ============================================
|
|
47
59
|
// SAVE STATE MANAGEMENT
|
|
48
60
|
// ============================================
|
package/src/core/snapshot.js
CHANGED
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
* ┌─────────────────────────┐
|
|
31
31
|
* │ 4. SERIALIZE │
|
|
32
32
|
* │ "<!DOCTYPE html>" │
|
|
33
|
-
* │ + outerHTML
|
|
33
|
+
* │ + outerHTML, unless a │
|
|
34
|
+
* │ save renderer is set │
|
|
35
|
+
* │ (setSaveRenderer) │
|
|
34
36
|
* │ │
|
|
35
37
|
* │ → sent to server │
|
|
36
38
|
* └─────────────────────────┘
|
|
@@ -40,6 +42,7 @@ import { stripExtensionNoise } from '../lib/extension-noise.js';
|
|
|
40
42
|
import { restoreAuthoredUrls } from '../lib/authored-url.js';
|
|
41
43
|
import { STRIP_FROM_SAVE, STRIP_FROM_COMPARISON, STRIP_FROM_DIRTY_CHECK, NO_TRIGGER_AUTOSAVE_SELECTOR, SNAPSHOT_REMOVE_SELECTOR } from '../lib/region-policy.js';
|
|
42
44
|
import { TAB_LOCAL_ROOT_ATTRS } from '../lib/root-attrs.js';
|
|
45
|
+
import { createContentView } from '../lib/content-dom.js';
|
|
43
46
|
|
|
44
47
|
// =============================================================================
|
|
45
48
|
// HOOK REGISTRIES
|
|
@@ -47,6 +50,20 @@ import { TAB_LOCAL_ROOT_ATTRS } from '../lib/root-attrs.js';
|
|
|
47
50
|
|
|
48
51
|
const snapshotHooks = []; // Phase 2: Always run (form sync)
|
|
49
52
|
const documentTransforms = []; // Phase 3a: Save and change check (strip admin)
|
|
53
|
+
const snapshotProvenance = new WeakMap();
|
|
54
|
+
|
|
55
|
+
// Phase 4, and there is only ever one. Everything else in this file is a list
|
|
56
|
+
// because many things want a turn; serializing the document is one decision, so a
|
|
57
|
+
// second renderer would be two answers to "what bytes does this save send".
|
|
58
|
+
let saveRenderer = null;
|
|
59
|
+
|
|
60
|
+
export function originalSnapshotNode(cloneNode) {
|
|
61
|
+
return snapshotProvenance.get(cloneNode) || null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function stripSnapshotRegions(clone) {
|
|
65
|
+
for (const el of clone.querySelectorAll(SNAPSHOT_REMOVE_SELECTOR)) el.remove();
|
|
66
|
+
}
|
|
50
67
|
|
|
51
68
|
/**
|
|
52
69
|
* Run every authored handler of one kind over a clone, and never let one of them
|
|
@@ -63,6 +80,8 @@ const documentTransforms = []; // Phase 3a: Save and change check (strip admin)
|
|
|
63
80
|
*/
|
|
64
81
|
function runAuthoredHandlers(clone, attr) {
|
|
65
82
|
for (const el of clone.querySelectorAll(`[${attr}]`)) {
|
|
83
|
+
stripSnapshotRegions(clone);
|
|
84
|
+
if (el !== clone && !clone.contains(el)) continue;
|
|
66
85
|
try {
|
|
67
86
|
new Function(el.getAttribute(attr)).call(el);
|
|
68
87
|
} catch (err) {
|
|
@@ -81,6 +100,94 @@ export function onSnapshot(callback) {
|
|
|
81
100
|
snapshotHooks.push(callback);
|
|
82
101
|
}
|
|
83
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Replace the save-domain serializer.
|
|
105
|
+
*
|
|
106
|
+
* Given the prepared save clone and the bytes `clone.outerHTML` would have sent, it
|
|
107
|
+
* returns the bytes to send instead. The source map (plugins/source.js) is the only
|
|
108
|
+
* caller: it renders the file's own bytes back out with only the changed regions
|
|
109
|
+
* reprinted, so a save stops rewriting 88% of an authored document's lines.
|
|
110
|
+
*
|
|
111
|
+
* It runs on the save path with a person's document in its hands, so the contract is
|
|
112
|
+
* narrow. It must return a complete document. It must not touch the clone, which is
|
|
113
|
+
* also the source of both comparison baselines. And it must be able to answer with
|
|
114
|
+
* `today` unchanged, because that is what it has to do whenever it is not certain:
|
|
115
|
+
* the caller here treats a throw the same way, so the floor of any renderer is the
|
|
116
|
+
* serialization it replaced.
|
|
117
|
+
*
|
|
118
|
+
* Comparison and dirty-check captures deliberately do NOT go through it. Those
|
|
119
|
+
* strings are only ever compared against each other, so they gain nothing from
|
|
120
|
+
* source fidelity and would pay a full render on every keystroke's dirty check.
|
|
121
|
+
*
|
|
122
|
+
* @param {?Function} renderer - (clone, today) => string, or null to restore outerHTML
|
|
123
|
+
*/
|
|
124
|
+
export function setSaveRenderer(renderer) {
|
|
125
|
+
saveRenderer = renderer;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const XHTML_NS = 'http://www.w3.org/1999/xhtml';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `clone.outerHTML`, except that a <noscript> holding only text is written raw.
|
|
132
|
+
*
|
|
133
|
+
* The page parsed with scripting on, so a <noscript> holds the author's markup as one
|
|
134
|
+
* text node, and the page's own serializer writes it back raw. The clone lives in a
|
|
135
|
+
* document with no window (createContentView), where scripting is off, and Chrome's
|
|
136
|
+
* serializer escapes that text there: `<p>` went to disk as `<p>`, which a reload
|
|
137
|
+
* reads as literal text, and every later save escaped it again. The text is swapped for
|
|
138
|
+
* a marker only while serializing, so the clone holds the same nodes when this returns.
|
|
139
|
+
*/
|
|
140
|
+
function serializeClone(clone) {
|
|
141
|
+
const blocks = [];
|
|
142
|
+
const collect = (node) => {
|
|
143
|
+
for (const child of node.childNodes) {
|
|
144
|
+
if (child.nodeType !== 1) continue;
|
|
145
|
+
if (child.localName === 'noscript' && child.namespaceURI === XHTML_NS) {
|
|
146
|
+
const kids = Array.from(child.childNodes);
|
|
147
|
+
if (kids.length && kids.every((n) => n.nodeType === 3)) blocks.push([child, kids]);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
collect(child.localName === 'template' && child.content ? child.content : child);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
collect(clone);
|
|
154
|
+
if (!blocks.length) return "<!DOCTYPE html>" + clone.outerHTML;
|
|
155
|
+
const nonce = Math.random().toString(36).slice(2);
|
|
156
|
+
const raw = blocks.map(([el, kids], i) => {
|
|
157
|
+
const marker = `\uE000clay-noscript-${nonce}-${i}\uE000`;
|
|
158
|
+
el.replaceChildren(el.ownerDocument.createTextNode(marker));
|
|
159
|
+
return [marker, kids.map((n) => n.data).join('')];
|
|
160
|
+
});
|
|
161
|
+
let html;
|
|
162
|
+
try {
|
|
163
|
+
html = "<!DOCTYPE html>" + clone.outerHTML;
|
|
164
|
+
} finally {
|
|
165
|
+
for (const [el, kids] of blocks) el.replaceChildren(...kids);
|
|
166
|
+
}
|
|
167
|
+
for (const [marker, text] of raw) html = html.split(marker).join(text);
|
|
168
|
+
return html;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The bytes a save sends: the prepared clone, through the save renderer if one is
|
|
173
|
+
* installed.
|
|
174
|
+
*
|
|
175
|
+
* The try/catch is not defensive noise around a renderer that already catches its
|
|
176
|
+
* own errors. That one catches in order to COUNT, and to fire the event that says a
|
|
177
|
+
* document reprinted; this one is the guarantee that no bug in an optional plugin can
|
|
178
|
+
* cost somebody a save.
|
|
179
|
+
*/
|
|
180
|
+
function serializeSaveClone(clone) {
|
|
181
|
+
const today = serializeClone(clone);
|
|
182
|
+
if (!saveRenderer) return today;
|
|
183
|
+
try {
|
|
184
|
+
return saveRenderer(clone, today);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error('clayjs: save renderer failed, sending the full serialization', err);
|
|
187
|
+
return today;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
84
191
|
/**
|
|
85
192
|
* Register a transform that runs over a detached clone when preparing to save.
|
|
86
193
|
* Use for: stripping admin elements, cleanup.
|
|
@@ -116,7 +223,7 @@ function clonePreventingOnclone(node, deep = true) {
|
|
|
116
223
|
finally { window.__preventOnclone = prev; }
|
|
117
224
|
}
|
|
118
225
|
|
|
119
|
-
export function captureSnapshot({ flushUndo = true } = {}) {
|
|
226
|
+
export function captureSnapshot({ flushUndo = true, authored = true } = {}) {
|
|
120
227
|
// Force-close any pending undo idle batch BEFORE cloning the DOM, so the
|
|
121
228
|
// snapshot reflects a clean undo boundary. Without this, a save that fires
|
|
122
229
|
// mid-typing would leave the idle batch open across the save boundary, and
|
|
@@ -129,10 +236,27 @@ export function captureSnapshot({ flushUndo = true } = {}) {
|
|
|
129
236
|
window.clay.undo.flush();
|
|
130
237
|
}
|
|
131
238
|
|
|
132
|
-
const
|
|
239
|
+
const view = createContentView(document.documentElement, { capability: 'snapshot' });
|
|
240
|
+
const clone = view.root;
|
|
241
|
+
// A <template>'s children are not its childNodes, they are its content fragment's,
|
|
242
|
+
// so walking childNodes alone leaves everything inside a template with no provenance.
|
|
243
|
+
// Nothing noticed until the source map asked which live node a clone node came from
|
|
244
|
+
// and got null for every node in a template, which reprinted the whole template on
|
|
245
|
+
// every save. createContentView already maps the fragment itself, so descending into
|
|
246
|
+
// it is all that was missing.
|
|
247
|
+
const remember = (node) => {
|
|
248
|
+
const live = view.original(node);
|
|
249
|
+
if (live) snapshotProvenance.set(node, live);
|
|
250
|
+
const children = node.nodeType === 1 && node.tagName === 'TEMPLATE' && node.content
|
|
251
|
+
? node.content.childNodes
|
|
252
|
+
: node.childNodes;
|
|
253
|
+
for (const child of children || []) remember(child);
|
|
254
|
+
};
|
|
255
|
+
remember(clone);
|
|
133
256
|
|
|
134
257
|
for (const hook of snapshotHooks) {
|
|
135
|
-
|
|
258
|
+
stripSnapshotRegions(clone);
|
|
259
|
+
hook(clone, { original: originalSnapshotNode });
|
|
136
260
|
}
|
|
137
261
|
|
|
138
262
|
// Put back any URL clay rewrote at runtime (cache-bust, refetch-on-save) so
|
|
@@ -140,11 +264,13 @@ export function captureSnapshot({ flushUndo = true } = {}) {
|
|
|
140
264
|
// authored handler sees the same URLs the file will.
|
|
141
265
|
restoreAuthoredUrls(clone);
|
|
142
266
|
|
|
143
|
-
|
|
267
|
+
stripSnapshotRegions(clone);
|
|
268
|
+
// authored: false is for a capture that is not a save and not a frame the page will
|
|
269
|
+
// see. The attributes hold page-author JavaScript, so running them is a side effect
|
|
270
|
+
// the author asked for once per save, not once per capture.
|
|
271
|
+
if (authored) runAuthoredHandlers(clone, 'onbeforesnapshot');
|
|
144
272
|
|
|
145
|
-
|
|
146
|
-
el.remove();
|
|
147
|
-
}
|
|
273
|
+
stripSnapshotRegions(clone);
|
|
148
274
|
|
|
149
275
|
// Browser-extension noise (password-manager menus, Grammarly overlays, and
|
|
150
276
|
// marker attributes on real inputs) is not page content. Drop it from every
|
|
@@ -159,17 +285,21 @@ export function captureSnapshot({ flushUndo = true } = {}) {
|
|
|
159
285
|
* Mutates the clone — only call once per snapshot.
|
|
160
286
|
*
|
|
161
287
|
* @param {HTMLElement} clone - A snapshot from captureSnapshot()
|
|
162
|
-
* @returns {
|
|
288
|
+
* @returns {HTMLElement} The same clone, in the save domain
|
|
163
289
|
*/
|
|
164
|
-
function prepareCloneForSave(clone) {
|
|
290
|
+
function prepareCloneForSave(clone, { authored = true } = {}) {
|
|
291
|
+
stripSnapshotRegions(clone);
|
|
165
292
|
// Run inline [onbeforesave] handlers
|
|
166
|
-
runAuthoredHandlers(clone, 'onbeforesave');
|
|
293
|
+
if (authored) runAuthoredHandlers(clone, 'onbeforesave');
|
|
167
294
|
|
|
168
295
|
// Run registered prepare hooks ([freeze]/[save-freeze] innerHTML restore lives here)
|
|
169
296
|
for (const hook of documentTransforms) {
|
|
297
|
+
stripSnapshotRegions(clone);
|
|
170
298
|
hook(clone);
|
|
171
299
|
}
|
|
172
300
|
|
|
301
|
+
stripSnapshotRegions(clone);
|
|
302
|
+
|
|
173
303
|
// Strip [no-save] / legacy [save-remove] LAST (snapshot-algorithm step 7): a
|
|
174
304
|
// prepare hook (freeze restore) can re-inject [no-save] content into the clone,
|
|
175
305
|
// so the strip must run after the hooks or that content leaks to disk.
|
|
@@ -177,7 +307,29 @@ function prepareCloneForSave(clone) {
|
|
|
177
307
|
el.remove();
|
|
178
308
|
}
|
|
179
309
|
|
|
180
|
-
return
|
|
310
|
+
return clone;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The save-domain clone, for a caller that needs the tree rather than the bytes.
|
|
315
|
+
*
|
|
316
|
+
* The source map pairs against this rather than against the live DOM, because this is
|
|
317
|
+
* the tree in the file's own domain: edit mode deactivated back to the inert
|
|
318
|
+
* attribute forms, [no-save] gone, every transform run. Every node in it that came
|
|
319
|
+
* from the page can be traced back with originalSnapshotNode.
|
|
320
|
+
*
|
|
321
|
+
* Emits no snapshot-ready event: this capture must never feed the send pipeline.
|
|
322
|
+
*
|
|
323
|
+
* A caller that is only INSPECTING the tree passes `{ flushUndo: false, authored:
|
|
324
|
+
* false }`. Both defaults are right for a save and wrong for anything else: flushing
|
|
325
|
+
* closes the undo batch, so a capture taken mid-typing splits the user's undo history
|
|
326
|
+
* at a point they did not make, and the authored handlers are page JavaScript that is
|
|
327
|
+
* meant to run once per save, not once per capture.
|
|
328
|
+
*
|
|
329
|
+
* @returns {HTMLElement}
|
|
330
|
+
*/
|
|
331
|
+
export function captureSaveClone({ flushUndo = true, authored = true } = {}) {
|
|
332
|
+
return prepareCloneForSave(captureSnapshot({ flushUndo, authored }), { authored });
|
|
181
333
|
}
|
|
182
334
|
|
|
183
335
|
/**
|
|
@@ -202,9 +354,12 @@ export function captureForComparison({ flushUndo = true } = {}) {
|
|
|
202
354
|
|
|
203
355
|
// Run registered prepare hooks
|
|
204
356
|
for (const hook of documentTransforms) {
|
|
357
|
+
stripSnapshotRegions(clone);
|
|
205
358
|
hook(clone);
|
|
206
359
|
}
|
|
207
360
|
|
|
361
|
+
stripSnapshotRegions(clone);
|
|
362
|
+
|
|
208
363
|
return "<!DOCTYPE html>" + clone.outerHTML;
|
|
209
364
|
}
|
|
210
365
|
|
|
@@ -232,9 +387,12 @@ export function captureForDirtyCheck({ flushUndo = true } = {}) {
|
|
|
232
387
|
}
|
|
233
388
|
|
|
234
389
|
for (const hook of documentTransforms) {
|
|
390
|
+
stripSnapshotRegions(clone);
|
|
235
391
|
hook(clone);
|
|
236
392
|
}
|
|
237
393
|
|
|
394
|
+
stripSnapshotRegions(clone);
|
|
395
|
+
|
|
238
396
|
return "<!DOCTYPE html>" + clone.outerHTML;
|
|
239
397
|
}
|
|
240
398
|
|
|
@@ -262,8 +420,10 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
|
|
|
262
420
|
el.remove();
|
|
263
421
|
}
|
|
264
422
|
for (const hook of documentTransforms) {
|
|
423
|
+
stripSnapshotRegions(clone);
|
|
265
424
|
hook(clone);
|
|
266
425
|
}
|
|
426
|
+
stripSnapshotRegions(clone);
|
|
267
427
|
const forComparison = "<!DOCTYPE html>" + clone.outerHTML;
|
|
268
428
|
|
|
269
429
|
let forDirty = forComparison;
|
|
@@ -272,8 +432,10 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
|
|
|
272
432
|
el.remove();
|
|
273
433
|
}
|
|
274
434
|
for (const hook of documentTransforms) {
|
|
435
|
+
stripSnapshotRegions(dirtyClone);
|
|
275
436
|
hook(dirtyClone);
|
|
276
437
|
}
|
|
438
|
+
stripSnapshotRegions(dirtyClone);
|
|
277
439
|
forDirty = "<!DOCTYPE html>" + dirtyClone.outerHTML;
|
|
278
440
|
}
|
|
279
441
|
|
|
@@ -324,20 +486,24 @@ export function captureForSaveAndComparison({ emitForSync = true } = {}) {
|
|
|
324
486
|
// Save clone: run hooks (freeze restore lives here), THEN strip [no-save]/[save-remove]
|
|
325
487
|
// LAST (snapshot-algorithm step 7) so freeze-restored [no-save] content can't leak to disk.
|
|
326
488
|
for (const hook of documentTransforms) {
|
|
489
|
+
stripSnapshotRegions(clone);
|
|
327
490
|
hook(clone);
|
|
328
491
|
}
|
|
492
|
+
stripSnapshotRegions(clone);
|
|
329
493
|
for (const el of clone.querySelectorAll(STRIP_FROM_SAVE)) {
|
|
330
494
|
el.remove();
|
|
331
495
|
}
|
|
332
|
-
const forSave =
|
|
496
|
+
const forSave = serializeSaveClone(clone);
|
|
333
497
|
|
|
334
498
|
// Compare clone: strip every autosave-off region, then run hooks
|
|
335
499
|
for (const el of compareClone.querySelectorAll(STRIP_FROM_COMPARISON)) {
|
|
336
500
|
el.remove();
|
|
337
501
|
}
|
|
338
502
|
for (const hook of documentTransforms) {
|
|
503
|
+
stripSnapshotRegions(compareClone);
|
|
339
504
|
hook(compareClone);
|
|
340
505
|
}
|
|
506
|
+
stripSnapshotRegions(compareClone);
|
|
341
507
|
const forComparison = "<!DOCTYPE html>" + compareClone.outerHTML;
|
|
342
508
|
|
|
343
509
|
// Dirty clone: same shape as the compare clone, one selector weaker.
|
|
@@ -347,8 +513,10 @@ export function captureForSaveAndComparison({ emitForSync = true } = {}) {
|
|
|
347
513
|
el.remove();
|
|
348
514
|
}
|
|
349
515
|
for (const hook of documentTransforms) {
|
|
516
|
+
stripSnapshotRegions(dirtyClone);
|
|
350
517
|
hook(dirtyClone);
|
|
351
518
|
}
|
|
519
|
+
stripSnapshotRegions(dirtyClone);
|
|
352
520
|
forDirty = "<!DOCTYPE html>" + dirtyClone.outerHTML;
|
|
353
521
|
}
|
|
354
522
|
|
|
@@ -400,8 +568,10 @@ export function captureForMerge() {
|
|
|
400
568
|
})(compareClone, clone);
|
|
401
569
|
|
|
402
570
|
for (const hook of documentTransforms) {
|
|
571
|
+
stripSnapshotRegions(clone);
|
|
403
572
|
hook(clone);
|
|
404
573
|
}
|
|
574
|
+
stripSnapshotRegions(clone);
|
|
405
575
|
for (const el of clone.querySelectorAll(STRIP_FROM_SAVE)) {
|
|
406
576
|
el.remove();
|
|
407
577
|
}
|
|
@@ -410,8 +580,10 @@ export function captureForMerge() {
|
|
|
410
580
|
el.remove();
|
|
411
581
|
}
|
|
412
582
|
for (const hook of documentTransforms) {
|
|
583
|
+
stripSnapshotRegions(compareClone);
|
|
413
584
|
hook(compareClone);
|
|
414
585
|
}
|
|
586
|
+
stripSnapshotRegions(compareClone);
|
|
415
587
|
|
|
416
588
|
return { saveClone: clone, compareClone, pairMap };
|
|
417
589
|
}
|
|
@@ -437,7 +609,7 @@ export function captureForSave({ emitForSync = true } = {}) {
|
|
|
437
609
|
}));
|
|
438
610
|
}
|
|
439
611
|
|
|
440
|
-
return prepareCloneForSave(clone);
|
|
612
|
+
return serializeSaveClone(prepareCloneForSave(clone));
|
|
441
613
|
}
|
|
442
614
|
|
|
443
615
|
/**
|