@panphora/clayjs 1.1.0 → 1.2.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/dist/clay.standalone.js +486 -189
- package/package.json +1 -1
- package/src/core/etag.js +17 -4
- package/src/core/host-attrs.js +52 -2
- package/src/core/is-edit-mode.js +14 -5
- package/src/core/save-conflict-notice.js +22 -27
- package/src/core/save-core.js +291 -26
- package/src/core/save.js +19 -4
- package/src/core/snapshot.js +5 -1
- package/src/core/stale-host-notice.js +95 -0
- package/src/lib/hostile-css.js +49 -0
- package/src/lib/root-attrs.js +52 -13
- package/src/loader-logic.js +5 -0
- package/src/sync/live-sync.js +184 -33
package/dist/clay.standalone.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* clayjs 1.
|
|
1
|
+
/* clayjs 1.2.0 standalone build: every clayjs module in one file. https://clayjs.com/offline
|
|
2
2
|
Third-party code inside keeps its own license (Sortable MIT, Squire MIT, DOMPurify Apache-2.0 OR MPL-2.0, MicroModal MIT): https://clayjs.com/THIRD-PARTY-NOTICES.md */
|
|
3
3
|
(() => {
|
|
4
4
|
var __create = Object.create;
|
|
@@ -105,12 +105,17 @@
|
|
|
105
105
|
function isTabLocalRootAttr(name, element) {
|
|
106
106
|
return element === document.documentElement && TAB_LOCAL_ROOT_ATTRS.has(name);
|
|
107
107
|
}
|
|
108
|
-
var SAVE_TOKEN_ATTRS, HOST_IDENTITY_ATTRS, HOST_TOKEN_ATTRS, ROOT_LIBRARY_ATTRS, TAB_LOCAL_ROOT_ATTRS;
|
|
108
|
+
var SAVE_TOKEN_ATTRS, LEGACY_SAVE_TOKEN_ATTRS, HOST_IDENTITY_ATTRS, HOST_TOKEN_ATTRS, ROOT_LIBRARY_ATTRS, TAB_LOCAL_ROOT_ATTRS;
|
|
109
109
|
var init_root_attrs = __esm({
|
|
110
110
|
"src/lib/root-attrs.js"() {
|
|
111
|
-
SAVE_TOKEN_ATTRS = ["savetoken"
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
SAVE_TOKEN_ATTRS = ["savetoken"];
|
|
112
|
+
LEGACY_SAVE_TOKEN_ATTRS = ["htmlclaytoken"];
|
|
113
|
+
HOST_IDENTITY_ATTRS = ["documentid", "htmlclayid"];
|
|
114
|
+
HOST_TOKEN_ATTRS = [
|
|
115
|
+
...SAVE_TOKEN_ATTRS,
|
|
116
|
+
...LEGACY_SAVE_TOKEN_ATTRS,
|
|
117
|
+
...HOST_IDENTITY_ATTRS
|
|
118
|
+
];
|
|
114
119
|
ROOT_LIBRARY_ATTRS = ["savestatus", "editmode", "pageowner"];
|
|
115
120
|
TAB_LOCAL_ROOT_ATTRS = /* @__PURE__ */ new Set([
|
|
116
121
|
...HOST_TOKEN_ATTRS,
|
|
@@ -120,20 +125,41 @@
|
|
|
120
125
|
});
|
|
121
126
|
|
|
122
127
|
// src/core/host-attrs.js
|
|
128
|
+
function warnAboutLegacyToken() {
|
|
129
|
+
if (warnedAboutLegacyToken) return;
|
|
130
|
+
const carries = LEGACY_SAVE_TOKEN_ATTRS.some(
|
|
131
|
+
(attr) => document.documentElement.getAttribute(attr)
|
|
132
|
+
);
|
|
133
|
+
if (!carries) return;
|
|
134
|
+
warnedAboutLegacyToken = true;
|
|
135
|
+
console.warn(
|
|
136
|
+
"[clay] This page was served with the pre-1.9.0 save token (" + LEGACY_SAVE_TOKEN_ATTRS.join(", ") + "), which this version no longer accepts. Saving will fail until the host is updated. If this is HTML Clay, upgrade it to 1.9.0 or newer."
|
|
137
|
+
);
|
|
138
|
+
}
|
|
123
139
|
function saveToken() {
|
|
124
140
|
if (typeof document === "undefined") return null;
|
|
125
141
|
for (const attr of SAVE_TOKEN_ATTRS) {
|
|
126
142
|
const value = document.documentElement.getAttribute(attr);
|
|
127
143
|
if (value) return value;
|
|
128
144
|
}
|
|
145
|
+
warnAboutLegacyToken();
|
|
129
146
|
return null;
|
|
130
147
|
}
|
|
131
148
|
function hasSaveToken() {
|
|
132
149
|
return saveToken() !== null;
|
|
133
150
|
}
|
|
151
|
+
function servedStaleToken() {
|
|
152
|
+
if (typeof document === "undefined") return false;
|
|
153
|
+
if (saveToken() !== null) return false;
|
|
154
|
+
return LEGACY_SAVE_TOKEN_ATTRS.some(
|
|
155
|
+
(attr) => document.documentElement.getAttribute(attr)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
var warnedAboutLegacyToken;
|
|
134
159
|
var init_host_attrs = __esm({
|
|
135
160
|
"src/core/host-attrs.js"() {
|
|
136
161
|
init_root_attrs();
|
|
162
|
+
warnedAboutLegacyToken = false;
|
|
137
163
|
}
|
|
138
164
|
});
|
|
139
165
|
|
|
@@ -157,17 +183,17 @@
|
|
|
157
183
|
forcedEditMode = Boolean(window.__hyperclayEditMode);
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
|
-
isEditMode = query_default.editmode ? query_default.editmode === "true" : forcedEditMode != null ? forcedEditMode : hasSaveToken() || Boolean(cookie_default.get("isAdminOfCurrentResource"));
|
|
186
|
+
isEditMode = query_default.editmode ? query_default.editmode === "true" : servedStaleToken() ? false : forcedEditMode != null ? forcedEditMode : hasSaveToken() || Boolean(cookie_default.get("isAdminOfCurrentResource"));
|
|
161
187
|
isOwner = Boolean(cookie_default.get("isAdminOfCurrentResource"));
|
|
162
188
|
}
|
|
163
189
|
});
|
|
164
190
|
|
|
165
191
|
// src/lib/extension-noise.js
|
|
166
|
-
function stripExtensionNoise(
|
|
167
|
-
if (!
|
|
192
|
+
function stripExtensionNoise(root3) {
|
|
193
|
+
if (!root3 || !root3.querySelectorAll) return;
|
|
168
194
|
try {
|
|
169
|
-
for (const el3 of
|
|
170
|
-
for (const el3 of [
|
|
195
|
+
for (const el3 of root3.querySelectorAll(EXTENSION_NODE_SELECTOR)) el3.remove();
|
|
196
|
+
for (const el3 of [root3, ...root3.querySelectorAll("*")]) {
|
|
171
197
|
if (!el3.attributes) continue;
|
|
172
198
|
for (const attr of [...el3.attributes]) {
|
|
173
199
|
if (EXTENSION_ATTR_PATTERN.test(attr.name.toLowerCase())) el3.removeAttribute(attr.name);
|
|
@@ -1326,7 +1352,7 @@
|
|
|
1326
1352
|
for (const name of TAB_LOCAL_ROOT_ATTRS) bareRoot.removeAttribute(name);
|
|
1327
1353
|
const shell = bareRoot.outerHTML;
|
|
1328
1354
|
const endTag = `</${bareRoot.localName}>`;
|
|
1329
|
-
return shell.slice(0, shell.length - endTag.length) + clone.innerHTML + endTag;
|
|
1355
|
+
return "<!DOCTYPE html>" + shell.slice(0, shell.length - endTag.length) + clone.innerHTML + endTag;
|
|
1330
1356
|
}
|
|
1331
1357
|
function captureBodyForSync() {
|
|
1332
1358
|
const clone = captureSnapshot();
|
|
@@ -1391,6 +1417,108 @@
|
|
|
1391
1417
|
}
|
|
1392
1418
|
});
|
|
1393
1419
|
|
|
1420
|
+
// src/lib/hostile-css.js
|
|
1421
|
+
function style(el3, rules) {
|
|
1422
|
+
for (const rule of rules) {
|
|
1423
|
+
const at = rule.indexOf(":");
|
|
1424
|
+
el3.style.setProperty(rule.slice(0, at).trim(), rule.slice(at + 1).trim(), "important");
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
function set(el3, prop, value) {
|
|
1428
|
+
el3.style.setProperty(prop, value, "important");
|
|
1429
|
+
}
|
|
1430
|
+
function make(tag, rules, text) {
|
|
1431
|
+
const el3 = document.createElement(tag);
|
|
1432
|
+
style(el3, rules);
|
|
1433
|
+
if (text) el3.textContent = text;
|
|
1434
|
+
return el3;
|
|
1435
|
+
}
|
|
1436
|
+
var init_hostile_css = __esm({
|
|
1437
|
+
"src/lib/hostile-css.js"() {
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
// src/core/stale-host-notice.js
|
|
1442
|
+
var stale_host_notice_exports = {};
|
|
1443
|
+
function place() {
|
|
1444
|
+
if (!root) return;
|
|
1445
|
+
const vv = window.visualViewport;
|
|
1446
|
+
const lift = vv ? Math.max(0, window.innerHeight - vv.height - vv.offsetTop) : 0;
|
|
1447
|
+
set(root, "bottom", `calc(${16 + lift}px + env(safe-area-inset-bottom,0px))`);
|
|
1448
|
+
}
|
|
1449
|
+
function dismiss() {
|
|
1450
|
+
if (!root) return;
|
|
1451
|
+
set(root, "display", "none");
|
|
1452
|
+
window.visualViewport?.removeEventListener("resize", place);
|
|
1453
|
+
window.visualViewport?.removeEventListener("scroll", place);
|
|
1454
|
+
}
|
|
1455
|
+
function build() {
|
|
1456
|
+
root = make("div", [
|
|
1457
|
+
"position:fixed",
|
|
1458
|
+
"left:50%",
|
|
1459
|
+
"transform:translateX(-50%)",
|
|
1460
|
+
"z-index:2147483001",
|
|
1461
|
+
"display:flex",
|
|
1462
|
+
"align-items:center",
|
|
1463
|
+
"gap:10px",
|
|
1464
|
+
"max-width:calc(100vw - 24px)",
|
|
1465
|
+
"flex-wrap:wrap",
|
|
1466
|
+
"justify-content:center",
|
|
1467
|
+
"padding:9px 12px",
|
|
1468
|
+
"border-radius:10px",
|
|
1469
|
+
`background:${BG}`,
|
|
1470
|
+
`color:${INK}`,
|
|
1471
|
+
`border:1px solid ${EDGE}`,
|
|
1472
|
+
"box-shadow:0 6px 24px rgba(0,0,0,.32),0 1px 2px rgba(0,0,0,.24)",
|
|
1473
|
+
`font:${FONT}`,
|
|
1474
|
+
"text-align:left"
|
|
1475
|
+
]);
|
|
1476
|
+
root.setAttribute("clay", "no-save no-watch no-snapshot");
|
|
1477
|
+
root.setAttribute("data-clay-stale-host", "");
|
|
1478
|
+
root.setAttribute("role", "alert");
|
|
1479
|
+
root.append(make("span", ["margin-right:2px"], MESSAGE));
|
|
1480
|
+
const close = make("button", [
|
|
1481
|
+
"all:initial",
|
|
1482
|
+
"box-sizing:border-box",
|
|
1483
|
+
"cursor:pointer",
|
|
1484
|
+
`font:${FONT}`,
|
|
1485
|
+
"color:" + INK,
|
|
1486
|
+
"opacity:.72",
|
|
1487
|
+
"padding:2px 6px",
|
|
1488
|
+
"border-radius:6px",
|
|
1489
|
+
"flex:none"
|
|
1490
|
+
], "Dismiss");
|
|
1491
|
+
close.type = "button";
|
|
1492
|
+
close.setAttribute("aria-label", "Dismiss this message");
|
|
1493
|
+
close.addEventListener("click", dismiss);
|
|
1494
|
+
root.append(close);
|
|
1495
|
+
document.body.appendChild(root);
|
|
1496
|
+
place();
|
|
1497
|
+
window.visualViewport?.addEventListener("resize", place);
|
|
1498
|
+
window.visualViewport?.addEventListener("scroll", place);
|
|
1499
|
+
}
|
|
1500
|
+
function init2() {
|
|
1501
|
+
if (!servedStaleToken()) return;
|
|
1502
|
+
if (isEditMode) return;
|
|
1503
|
+
build();
|
|
1504
|
+
}
|
|
1505
|
+
var BG, INK, EDGE, FONT, MESSAGE, root;
|
|
1506
|
+
var init_stale_host_notice = __esm({
|
|
1507
|
+
"src/core/stale-host-notice.js"() {
|
|
1508
|
+
init_host_attrs();
|
|
1509
|
+
init_is_edit_mode();
|
|
1510
|
+
init_dom_ready();
|
|
1511
|
+
init_hostile_css();
|
|
1512
|
+
BG = "var(--clay-notice-bg,#222)";
|
|
1513
|
+
INK = "var(--clay-notice-ink,#fff)";
|
|
1514
|
+
EDGE = "var(--clay-notice-edge,rgba(255,255,255,.28))";
|
|
1515
|
+
FONT = "14px/1.45 system-ui,-apple-system,'Segoe UI',sans-serif";
|
|
1516
|
+
MESSAGE = "This page can't be edited: the app serving it is out of date. Update HTML Clay to 1.9.0 or newer.";
|
|
1517
|
+
root = null;
|
|
1518
|
+
dom_ready_default(init2);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1394
1522
|
// src/core/host-meta.js
|
|
1395
1523
|
function bareHost() {
|
|
1396
1524
|
return { spec: null, extensions: [], document: null };
|
|
@@ -1499,6 +1627,7 @@
|
|
|
1499
1627
|
if (isEditMode) {
|
|
1500
1628
|
document.addEventListener("clay:sync-applied", (event) => {
|
|
1501
1629
|
if (event.detail?.source !== "disk") return;
|
|
1630
|
+
if (typeof event.detail.etag === "string" && event.detail.etag) return;
|
|
1502
1631
|
forgetEtag();
|
|
1503
1632
|
seedEtag({ fresh: true });
|
|
1504
1633
|
});
|
|
@@ -1514,6 +1643,8 @@
|
|
|
1514
1643
|
isSaveInProgress: () => isSaveInProgress,
|
|
1515
1644
|
onSnapshot: () => onSnapshot,
|
|
1516
1645
|
replacePageWith: () => replacePageWith,
|
|
1646
|
+
resetSaveAttempts: () => resetSaveAttempts,
|
|
1647
|
+
saveFateIsUnknown: () => saveFateIsUnknown,
|
|
1517
1648
|
saveHtml: () => saveHtml
|
|
1518
1649
|
});
|
|
1519
1650
|
function isSaveInProgress() {
|
|
@@ -1528,9 +1659,34 @@
|
|
|
1528
1659
|
etag: data.etag ?? null
|
|
1529
1660
|
};
|
|
1530
1661
|
}
|
|
1662
|
+
function saveFateIsUnknown() {
|
|
1663
|
+
return unknownAttempt !== null;
|
|
1664
|
+
}
|
|
1665
|
+
function mintSaveId() {
|
|
1666
|
+
const uuid = window.crypto?.randomUUID?.();
|
|
1667
|
+
if (uuid) return uuid;
|
|
1668
|
+
if (window.crypto?.getRandomValues) {
|
|
1669
|
+
const bytes = new Uint8Array(16);
|
|
1670
|
+
window.crypto.getRandomValues(bytes);
|
|
1671
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1672
|
+
}
|
|
1673
|
+
const segment = () => Math.random().toString(36).slice(2, 12).padEnd(10, "0");
|
|
1674
|
+
return `${Date.now().toString(36)}-${segment()}${segment()}${segment()}`;
|
|
1675
|
+
}
|
|
1676
|
+
function rememberSentId(saveId) {
|
|
1677
|
+
sentIds.push(saveId);
|
|
1678
|
+
if (sentIds.length > SENT_ID_MEMORY) sentIds.shift();
|
|
1679
|
+
}
|
|
1680
|
+
function sentByThisTab(saveId) {
|
|
1681
|
+
return typeof saveId === "string" && saveId !== "" && sentIds.includes(saveId);
|
|
1682
|
+
}
|
|
1683
|
+
function resetSaveAttempts() {
|
|
1684
|
+
sentIds.length = 0;
|
|
1685
|
+
unknownAttempt = null;
|
|
1686
|
+
}
|
|
1531
1687
|
function errorResult(err) {
|
|
1532
1688
|
const timedOut = err.name === "AbortError";
|
|
1533
|
-
const conflicted = !timedOut &&
|
|
1689
|
+
const conflicted = !timedOut && err.status === 412;
|
|
1534
1690
|
return {
|
|
1535
1691
|
ok: false,
|
|
1536
1692
|
msg: timedOut ? "Server not responding" : err.message || "Save failed",
|
|
@@ -1538,13 +1694,17 @@
|
|
|
1538
1694
|
// so this reports "we do not know" rather than asserting something false.
|
|
1539
1695
|
msgType: timedOut ? "unknown" : conflicted ? "conflict" : "error",
|
|
1540
1696
|
code: timedOut ? "timeout" : conflicted ? "conflict" : err.code ?? null,
|
|
1697
|
+
changedBy: conflicted ? err.changedBy ?? null : null,
|
|
1698
|
+
// A refusal that may be answering this tab's own timed-out write. Only the
|
|
1699
|
+
// notice uses it, and only to word itself; nothing decides anything on it.
|
|
1700
|
+
afterTimeout: conflicted && unknownAttempt !== null,
|
|
1541
1701
|
etag: null
|
|
1542
1702
|
};
|
|
1543
1703
|
}
|
|
1544
1704
|
function skippedResult(msg) {
|
|
1545
1705
|
return { ok: false, msg, msgType: "skipped", code: null, etag: null };
|
|
1546
1706
|
}
|
|
1547
|
-
function buildSaveRequest(content, userDriven, signal) {
|
|
1707
|
+
function buildSaveRequest(content, userDriven, signal, saveId, etag) {
|
|
1548
1708
|
const token = saveToken();
|
|
1549
1709
|
const path = token ? `${SAVE_PATH}/${token}` : SAVE_PATH;
|
|
1550
1710
|
const options = {
|
|
@@ -1558,26 +1718,36 @@
|
|
|
1558
1718
|
// Spec §9's name for the provenance bit. `X-Hyperclay-User-Driven` was the
|
|
1559
1719
|
// pre-spec spelling; every host reads Save-Trigger first and falls back to
|
|
1560
1720
|
// it, so stored documents running an older client keep working.
|
|
1561
|
-
"Save-Trigger": userDriven ? "user" : "auto"
|
|
1721
|
+
"Save-Trigger": userDriven ? "user" : "auto",
|
|
1722
|
+
// Spec §6 (`receipts`): which attempt this is. Sent to every host, because a
|
|
1723
|
+
// host that does not implement receipts ignores an unknown header, and
|
|
1724
|
+
// gating it on discovery would mean holding the save until an async lookup
|
|
1725
|
+
// resolved. Nothing is inferred from the header being accepted: only a host
|
|
1726
|
+
// that ANSWERS with the id has said anything.
|
|
1727
|
+
"Save-ID": saveId
|
|
1562
1728
|
},
|
|
1563
1729
|
body: content
|
|
1564
1730
|
};
|
|
1565
|
-
|
|
1566
|
-
if (conditionalSaves() && etag) options.headers["If-Match"] = etag;
|
|
1731
|
+
if (etag) options.headers["If-Match"] = etag;
|
|
1567
1732
|
return { url: resolveSaveUrl(path), options };
|
|
1568
1733
|
}
|
|
1734
|
+
function stampToSend() {
|
|
1735
|
+
const etag = lastSeenEtag();
|
|
1736
|
+
return conditionalSaves() && etag ? etag : null;
|
|
1737
|
+
}
|
|
1569
1738
|
function resolveSaveUrl(path) {
|
|
1570
1739
|
const origin = window.location.origin;
|
|
1571
1740
|
if (!origin || origin === "null") return path;
|
|
1572
1741
|
return new URL(path, origin).href;
|
|
1573
1742
|
}
|
|
1574
|
-
function
|
|
1743
|
+
function sendOnce(content, saveId, etag) {
|
|
1575
1744
|
const controller = new AbortController();
|
|
1576
1745
|
const timeoutId = setTimeout(() => controller.abort(), SAVE_TIMEOUT_MS);
|
|
1577
1746
|
const gestureDriven = consumeUserDriven();
|
|
1578
1747
|
const explicitlyAsked = consumeExplicitSave();
|
|
1579
1748
|
const userDriven = gestureDriven || explicitlyAsked;
|
|
1580
|
-
const { url, options } = buildSaveRequest(content, userDriven, controller.signal);
|
|
1749
|
+
const { url, options } = buildSaveRequest(content, userDriven, controller.signal, saveId, etag);
|
|
1750
|
+
rememberSentId(saveId);
|
|
1581
1751
|
return fetch(url, options).then((res) => res.text().then((text) => {
|
|
1582
1752
|
let data = {};
|
|
1583
1753
|
if (text) {
|
|
@@ -1591,19 +1761,68 @@
|
|
|
1591
1761
|
const error = new Error(data.msg || data.error || `HTTP ${res.status}: ${res.statusText}`);
|
|
1592
1762
|
error.code = data.code ?? null;
|
|
1593
1763
|
error.status = res.status;
|
|
1764
|
+
error.changedBy = data.changedBy ?? null;
|
|
1765
|
+
error.etag = data.etag ?? null;
|
|
1766
|
+
error.saveId = data.saveId ?? null;
|
|
1594
1767
|
throw error;
|
|
1595
1768
|
}
|
|
1596
1769
|
recordEtag(data.etag ?? null);
|
|
1770
|
+
unknownAttempt = null;
|
|
1597
1771
|
return data;
|
|
1598
1772
|
})).catch((err) => {
|
|
1599
1773
|
console.error("Failed to save page:", err);
|
|
1600
|
-
if (err.name === "AbortError")
|
|
1774
|
+
if (err.name === "AbortError") unknownAttempt = { saveId, etag };
|
|
1601
1775
|
if (userDriven) markUserDriven();
|
|
1602
1776
|
throw err;
|
|
1603
1777
|
}).finally(() => {
|
|
1604
1778
|
clearTimeout(timeoutId);
|
|
1605
1779
|
});
|
|
1606
1780
|
}
|
|
1781
|
+
async function recoverUnknownSave() {
|
|
1782
|
+
const attempt = unknownAttempt;
|
|
1783
|
+
if (!attempt) return { landed: null, resend: false };
|
|
1784
|
+
const meta = await hostMeta({ fresh: true });
|
|
1785
|
+
const doc = meta.document;
|
|
1786
|
+
const stored = typeof doc?.etag === "string" && doc.etag ? doc.etag : null;
|
|
1787
|
+
if (!stored) return { landed: null, resend: false };
|
|
1788
|
+
if (doc.saveId && doc.saveId === attempt.saveId) {
|
|
1789
|
+
recordEtag(stored);
|
|
1790
|
+
unknownAttempt = null;
|
|
1791
|
+
return { landed: { msg: "Saved", msgType: "success", etag: stored }, resend: false };
|
|
1792
|
+
}
|
|
1793
|
+
if (doc.saveId) unknownAttempt = null;
|
|
1794
|
+
return { landed: null, resend: !!attempt.etag, etag: attempt.etag };
|
|
1795
|
+
}
|
|
1796
|
+
async function sendSave(content) {
|
|
1797
|
+
let saveId = mintSaveId();
|
|
1798
|
+
let etag = stampToSend();
|
|
1799
|
+
let reSent = false;
|
|
1800
|
+
let duplicateResolved = false;
|
|
1801
|
+
for (; ; ) {
|
|
1802
|
+
try {
|
|
1803
|
+
return await sendOnce(content, saveId, etag);
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
if (err.name === "AbortError") {
|
|
1806
|
+
if (reSent) throw err;
|
|
1807
|
+
const { landed, resend, etag: original } = await recoverUnknownSave();
|
|
1808
|
+
if (landed) return landed;
|
|
1809
|
+
if (!resend) throw err;
|
|
1810
|
+
etag = original;
|
|
1811
|
+
reSent = true;
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
if (err.status === 412 && !duplicateResolved && sentByThisTab(err.saveId)) {
|
|
1815
|
+
recordEtag(err.etag ?? null);
|
|
1816
|
+
unknownAttempt = null;
|
|
1817
|
+
saveId = mintSaveId();
|
|
1818
|
+
etag = stampToSend();
|
|
1819
|
+
duplicateResolved = true;
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
throw err;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1607
1826
|
function saveHtml(html, callback = () => {
|
|
1608
1827
|
}) {
|
|
1609
1828
|
return new Promise((resolve) => {
|
|
@@ -1651,17 +1870,21 @@
|
|
|
1651
1870
|
});
|
|
1652
1871
|
});
|
|
1653
1872
|
}
|
|
1654
|
-
var saveInProgress, SAVE_PATH, SAVE_TIMEOUT_MS;
|
|
1873
|
+
var saveInProgress, SAVE_PATH, SAVE_TIMEOUT_MS, sentIds, SENT_ID_MEMORY, unknownAttempt;
|
|
1655
1874
|
var init_save_core = __esm({
|
|
1656
1875
|
"src/core/save-core.js"() {
|
|
1657
1876
|
init_is_edit_mode();
|
|
1658
1877
|
init_user_gesture();
|
|
1659
1878
|
init_host_attrs();
|
|
1660
1879
|
init_etag();
|
|
1880
|
+
init_host_meta();
|
|
1661
1881
|
init_snapshot();
|
|
1662
1882
|
saveInProgress = false;
|
|
1663
1883
|
SAVE_PATH = "/_/save";
|
|
1664
1884
|
SAVE_TIMEOUT_MS = 12e3;
|
|
1885
|
+
sentIds = [];
|
|
1886
|
+
SENT_ID_MEMORY = 8;
|
|
1887
|
+
unknownAttempt = null;
|
|
1665
1888
|
}
|
|
1666
1889
|
});
|
|
1667
1890
|
|
|
@@ -1954,7 +2177,7 @@
|
|
|
1954
2177
|
getLastSavedDirty: () => getLastSavedDirty,
|
|
1955
2178
|
getPageContents: () => getPageContents,
|
|
1956
2179
|
getUnsavedChanges: () => getUnsavedChanges,
|
|
1957
|
-
init: () =>
|
|
2180
|
+
init: () => init3,
|
|
1958
2181
|
initHyperclaySaveButton: () => initHyperclaySaveButton,
|
|
1959
2182
|
initSaveKeyboardShortcut: () => initSaveKeyboardShortcut,
|
|
1960
2183
|
isSaveConflicted: () => isSaveConflicted,
|
|
@@ -1969,14 +2192,14 @@
|
|
|
1969
2192
|
setUnsavedChanges: () => setUnsavedChanges,
|
|
1970
2193
|
suspendAutosave: () => suspendAutosave
|
|
1971
2194
|
});
|
|
1972
|
-
function setSaveState(state, msg = "", msgType = "") {
|
|
2195
|
+
function setSaveState(state, msg = "", msgType = "", extra = null) {
|
|
1973
2196
|
if (savingTimeout) {
|
|
1974
2197
|
clearTimeout(savingTimeout);
|
|
1975
2198
|
savingTimeout = null;
|
|
1976
2199
|
}
|
|
1977
2200
|
document.documentElement.setAttribute("savestatus", state);
|
|
1978
2201
|
const event = new CustomEvent(`clay:save-${state}`, {
|
|
1979
|
-
detail: { msg, msgType, timestamp: Date.now() }
|
|
2202
|
+
detail: { msg, msgType, timestamp: Date.now(), ...extra || {} }
|
|
1980
2203
|
});
|
|
1981
2204
|
document.dispatchEvent(event);
|
|
1982
2205
|
}
|
|
@@ -2029,7 +2252,10 @@
|
|
|
2029
2252
|
releaseConflictHold();
|
|
2030
2253
|
} else if (result2.msgType === "conflict") {
|
|
2031
2254
|
holdForConflict();
|
|
2032
|
-
setSaveState("conflict", result2.msg, result2.msgType
|
|
2255
|
+
setSaveState("conflict", result2.msg, result2.msgType, {
|
|
2256
|
+
changedBy: result2.changedBy ?? null,
|
|
2257
|
+
afterTimeout: result2.afterTimeout === true
|
|
2258
|
+
});
|
|
2033
2259
|
} else if (result2.msgType !== "skipped") {
|
|
2034
2260
|
if (!navigator.onLine) {
|
|
2035
2261
|
setSaveState("offline", result2.msg);
|
|
@@ -2040,6 +2266,7 @@
|
|
|
2040
2266
|
}
|
|
2041
2267
|
function drainPendingSave() {
|
|
2042
2268
|
if (!pendingSave) return;
|
|
2269
|
+
if (saveFateIsUnknown()) return;
|
|
2043
2270
|
pendingSave = false;
|
|
2044
2271
|
savePage();
|
|
2045
2272
|
}
|
|
@@ -2272,7 +2499,7 @@
|
|
|
2272
2499
|
}
|
|
2273
2500
|
});
|
|
2274
2501
|
}
|
|
2275
|
-
function
|
|
2502
|
+
function init3() {
|
|
2276
2503
|
if (!isEditMode) return;
|
|
2277
2504
|
seedEtag();
|
|
2278
2505
|
initUserGesture();
|
|
@@ -2324,34 +2551,19 @@
|
|
|
2324
2551
|
} else {
|
|
2325
2552
|
initBaselineCapture();
|
|
2326
2553
|
}
|
|
2327
|
-
|
|
2554
|
+
init3();
|
|
2328
2555
|
save_default = savePage;
|
|
2329
2556
|
}
|
|
2330
2557
|
});
|
|
2331
2558
|
|
|
2332
2559
|
// src/core/save-conflict-notice.js
|
|
2333
2560
|
var save_conflict_notice_exports = {};
|
|
2334
|
-
function style(el3, rules) {
|
|
2335
|
-
for (const rule of rules) {
|
|
2336
|
-
const at = rule.indexOf(":");
|
|
2337
|
-
el3.style.setProperty(rule.slice(0, at).trim(), rule.slice(at + 1).trim(), "important");
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
function set(el3, prop, value) {
|
|
2341
|
-
el3.style.setProperty(prop, value, "important");
|
|
2342
|
-
}
|
|
2343
|
-
function make(tag, rules, text) {
|
|
2344
|
-
const el3 = document.createElement(tag);
|
|
2345
|
-
style(el3, rules);
|
|
2346
|
-
if (text) el3.textContent = text;
|
|
2347
|
-
return el3;
|
|
2348
|
-
}
|
|
2349
2561
|
function button(label, rules) {
|
|
2350
2562
|
const b = make("button", [
|
|
2351
2563
|
"all:initial",
|
|
2352
2564
|
"box-sizing:border-box",
|
|
2353
2565
|
"cursor:pointer",
|
|
2354
|
-
`font:${
|
|
2566
|
+
`font:${FONT2}`,
|
|
2355
2567
|
"font-weight:500",
|
|
2356
2568
|
"border-radius:6px",
|
|
2357
2569
|
"padding:6px 12px",
|
|
@@ -2367,14 +2579,14 @@
|
|
|
2367
2579
|
b.addEventListener("blur", () => set(b, "outline", "none"));
|
|
2368
2580
|
return b;
|
|
2369
2581
|
}
|
|
2370
|
-
function
|
|
2371
|
-
if (!
|
|
2582
|
+
function place2() {
|
|
2583
|
+
if (!root2) return;
|
|
2372
2584
|
const vv = window.visualViewport;
|
|
2373
2585
|
const lift = vv ? Math.max(0, window.innerHeight - vv.height - vv.offsetTop) : 0;
|
|
2374
|
-
set(
|
|
2586
|
+
set(root2, "bottom", `calc(${16 + lift}px + env(safe-area-inset-bottom,0px))`);
|
|
2375
2587
|
}
|
|
2376
|
-
function
|
|
2377
|
-
|
|
2588
|
+
function build2() {
|
|
2589
|
+
root2 = make("div", [
|
|
2378
2590
|
"position:fixed",
|
|
2379
2591
|
"left:50%",
|
|
2380
2592
|
"transform:translateX(-50%)",
|
|
@@ -2387,31 +2599,31 @@
|
|
|
2387
2599
|
"justify-content:center",
|
|
2388
2600
|
"padding:9px 12px",
|
|
2389
2601
|
"border-radius:10px",
|
|
2390
|
-
`background:${
|
|
2391
|
-
`color:${
|
|
2392
|
-
`border:1px solid ${
|
|
2602
|
+
`background:${BG2}`,
|
|
2603
|
+
`color:${INK2}`,
|
|
2604
|
+
`border:1px solid ${EDGE2}`,
|
|
2393
2605
|
"box-shadow:0 6px 24px rgba(0,0,0,.32),0 1px 2px rgba(0,0,0,.24)",
|
|
2394
|
-
`font:${
|
|
2606
|
+
`font:${FONT2}`,
|
|
2395
2607
|
"text-align:left"
|
|
2396
2608
|
]);
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2609
|
+
root2.setAttribute("clay", "no-save no-watch no-snapshot");
|
|
2610
|
+
root2.setAttribute("data-clay-conflict", "");
|
|
2611
|
+
root2.setAttribute("role", "alert");
|
|
2400
2612
|
line = make("span", ["margin-right:2px"]);
|
|
2401
|
-
drop = button("Load theirs", [`color:${
|
|
2402
|
-
keep = button("Keep mine", [`background:${
|
|
2613
|
+
drop = button("Load theirs", [`color:${INK2}`, "opacity:.72", "padding:6px 8px"]);
|
|
2614
|
+
keep = button("Keep mine", [`background:${INK2}`, `color:${BG2}`, "font-weight:600"]);
|
|
2403
2615
|
drop.addEventListener("click", onDrop);
|
|
2404
2616
|
keep.addEventListener("click", onKeep);
|
|
2405
|
-
|
|
2617
|
+
root2.append(line, drop, keep);
|
|
2406
2618
|
if (!still()) {
|
|
2407
|
-
set(
|
|
2408
|
-
set(
|
|
2619
|
+
set(root2, "transition", "opacity .18s");
|
|
2620
|
+
set(root2, "opacity", "0");
|
|
2409
2621
|
}
|
|
2410
|
-
document.body.appendChild(
|
|
2411
|
-
|
|
2412
|
-
window.visualViewport?.addEventListener("resize",
|
|
2413
|
-
window.visualViewport?.addEventListener("scroll",
|
|
2414
|
-
requestAnimationFrame(() => set(
|
|
2622
|
+
document.body.appendChild(root2);
|
|
2623
|
+
place2();
|
|
2624
|
+
window.visualViewport?.addEventListener("resize", place2);
|
|
2625
|
+
window.visualViewport?.addEventListener("scroll", place2);
|
|
2626
|
+
requestAnimationFrame(() => set(root2, "opacity", "1"));
|
|
2415
2627
|
}
|
|
2416
2628
|
function onDrop() {
|
|
2417
2629
|
if (armed) {
|
|
@@ -2432,7 +2644,7 @@
|
|
|
2432
2644
|
armed = false;
|
|
2433
2645
|
drop.textContent = "Load theirs";
|
|
2434
2646
|
set(drop, "opacity", ".72");
|
|
2435
|
-
set(drop, "color",
|
|
2647
|
+
set(drop, "color", INK2);
|
|
2436
2648
|
set(drop, "box-shadow", "none");
|
|
2437
2649
|
}
|
|
2438
2650
|
async function onKeep() {
|
|
@@ -2461,44 +2673,49 @@
|
|
|
2461
2673
|
}
|
|
2462
2674
|
}
|
|
2463
2675
|
function show(e) {
|
|
2464
|
-
if (!
|
|
2465
|
-
set(
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2676
|
+
if (!root2) build2();
|
|
2677
|
+
set(root2, "display", "flex");
|
|
2678
|
+
const named = SOURCES[e?.detail?.changedBy];
|
|
2679
|
+
if (!named && e?.detail?.afterTimeout) {
|
|
2680
|
+
line.textContent = "This page changed elsewhere, possibly by your own save that timed out. Your edits here are safe, and nothing will be overwritten until you choose.";
|
|
2681
|
+
} else {
|
|
2682
|
+
line.textContent = `This page changed ${named || "elsewhere"}. Your edits here are safe, and nothing will be overwritten until you choose.`;
|
|
2683
|
+
}
|
|
2468
2684
|
keep.textContent = "Keep mine";
|
|
2469
2685
|
set(keep, "opacity", "1");
|
|
2470
2686
|
busy = false;
|
|
2471
2687
|
disarm();
|
|
2472
|
-
|
|
2688
|
+
place2();
|
|
2473
2689
|
}
|
|
2474
2690
|
function hide() {
|
|
2475
|
-
if (!
|
|
2691
|
+
if (!root2) return;
|
|
2476
2692
|
disarm();
|
|
2477
|
-
set(
|
|
2693
|
+
set(root2, "display", "none");
|
|
2478
2694
|
}
|
|
2479
|
-
function
|
|
2695
|
+
function init4() {
|
|
2480
2696
|
if (!isEditMode) return;
|
|
2481
2697
|
document.addEventListener("clay:save-conflict", show);
|
|
2482
2698
|
document.addEventListener("clay:save-saved", hide);
|
|
2483
2699
|
document.addEventListener("keydown", onKeydown);
|
|
2484
2700
|
}
|
|
2485
|
-
var
|
|
2701
|
+
var BG2, INK2, EDGE2, WARN, FONT2, SOURCES, ARM_MS, root2, line, keep, drop, armTimer, armed, busy, still;
|
|
2486
2702
|
var init_save_conflict_notice = __esm({
|
|
2487
2703
|
"src/core/save-conflict-notice.js"() {
|
|
2488
2704
|
init_is_edit_mode();
|
|
2489
2705
|
init_dom_ready();
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2706
|
+
init_hostile_css();
|
|
2707
|
+
BG2 = "var(--clay-conflict-bg,#222)";
|
|
2708
|
+
INK2 = "var(--clay-conflict-ink,#fff)";
|
|
2709
|
+
EDGE2 = "var(--clay-conflict-edge,rgba(255,255,255,.28))";
|
|
2493
2710
|
WARN = "var(--clay-conflict-warn,#e3a33f)";
|
|
2494
|
-
|
|
2711
|
+
FONT2 = "14px/1.45 system-ui,-apple-system,'Segoe UI',sans-serif";
|
|
2495
2712
|
SOURCES = {
|
|
2496
2713
|
"another-tab": "in another tab",
|
|
2497
2714
|
"another-person": "by someone else",
|
|
2498
2715
|
"an-agent": "by an agent"
|
|
2499
2716
|
};
|
|
2500
2717
|
ARM_MS = 5e3;
|
|
2501
|
-
|
|
2718
|
+
root2 = null;
|
|
2502
2719
|
line = null;
|
|
2503
2720
|
keep = null;
|
|
2504
2721
|
drop = null;
|
|
@@ -2506,7 +2723,7 @@
|
|
|
2506
2723
|
armed = false;
|
|
2507
2724
|
busy = false;
|
|
2508
2725
|
still = () => !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
2509
|
-
dom_ready_default(
|
|
2726
|
+
dom_ready_default(init4);
|
|
2510
2727
|
}
|
|
2511
2728
|
});
|
|
2512
2729
|
|
|
@@ -2595,7 +2812,7 @@
|
|
|
2595
2812
|
var persist_exports = {};
|
|
2596
2813
|
__export(persist_exports, {
|
|
2597
2814
|
default: () => enablePersistentFormInputValues,
|
|
2598
|
-
init: () =>
|
|
2815
|
+
init: () => init5
|
|
2599
2816
|
});
|
|
2600
2817
|
function enablePersistentFormInputValues(filterBySelector = PERSIST2) {
|
|
2601
2818
|
const inputSelector = `input${filterBySelector}:not([type="password"]):not([type="hidden"]):not([type="file"])`;
|
|
@@ -2628,7 +2845,7 @@
|
|
|
2628
2845
|
finalize(selectSelector);
|
|
2629
2846
|
});
|
|
2630
2847
|
}
|
|
2631
|
-
function
|
|
2848
|
+
function init5() {
|
|
2632
2849
|
enablePersistentFormInputValues(PERSIST2);
|
|
2633
2850
|
}
|
|
2634
2851
|
var init_persist = __esm({
|
|
@@ -2636,7 +2853,7 @@
|
|
|
2636
2853
|
init_snapshot();
|
|
2637
2854
|
init_control_serialize_vendor();
|
|
2638
2855
|
init_attr_aliases();
|
|
2639
|
-
|
|
2856
|
+
init5();
|
|
2640
2857
|
}
|
|
2641
2858
|
});
|
|
2642
2859
|
|
|
@@ -2656,15 +2873,15 @@
|
|
|
2656
2873
|
enableContentEditable();
|
|
2657
2874
|
});
|
|
2658
2875
|
}
|
|
2659
|
-
function enableContentEditable(
|
|
2660
|
-
|
|
2876
|
+
function enableContentEditable(root3 = document) {
|
|
2877
|
+
root3.querySelectorAll(SELECTOR).forEach((el3) => {
|
|
2661
2878
|
let val = el3.getAttribute("inert-contenteditable");
|
|
2662
2879
|
if (!["false", "plaintext-only"].includes(val)) val = "true";
|
|
2663
2880
|
el3.setAttribute("contenteditable", val);
|
|
2664
2881
|
el3.removeAttribute("inert-contenteditable");
|
|
2665
2882
|
});
|
|
2666
2883
|
}
|
|
2667
|
-
function
|
|
2884
|
+
function init6() {
|
|
2668
2885
|
disableContentEditableBeforeSave();
|
|
2669
2886
|
enableContentEditableForAdminOnPageLoad();
|
|
2670
2887
|
}
|
|
@@ -2695,15 +2912,15 @@
|
|
|
2695
2912
|
enableAdminInputs();
|
|
2696
2913
|
});
|
|
2697
2914
|
}
|
|
2698
|
-
function enableAdminInputs(
|
|
2699
|
-
|
|
2915
|
+
function enableAdminInputs(root3 = document) {
|
|
2916
|
+
root3.querySelectorAll(SELECTOR_DISABLED).forEach((input) => {
|
|
2700
2917
|
input.removeAttribute("disabled");
|
|
2701
2918
|
});
|
|
2702
|
-
|
|
2919
|
+
root3.querySelectorAll(SELECTOR_READONLY).forEach((input) => {
|
|
2703
2920
|
input.removeAttribute("readonly");
|
|
2704
2921
|
});
|
|
2705
2922
|
}
|
|
2706
|
-
function
|
|
2923
|
+
function init7() {
|
|
2707
2924
|
disableAdminInputsBeforeSave();
|
|
2708
2925
|
enableAdminInputsOnPageLoad();
|
|
2709
2926
|
}
|
|
@@ -2734,8 +2951,8 @@
|
|
|
2734
2951
|
enableOnClick();
|
|
2735
2952
|
});
|
|
2736
2953
|
}
|
|
2737
|
-
function enableOnClick(
|
|
2738
|
-
|
|
2954
|
+
function enableOnClick(root3 = document) {
|
|
2955
|
+
root3.querySelectorAll(SELECTOR2).forEach((el3) => {
|
|
2739
2956
|
const val = el3.getAttribute("inert-onclick");
|
|
2740
2957
|
if (val) {
|
|
2741
2958
|
el3.setAttribute("onclick", val);
|
|
@@ -2743,7 +2960,7 @@
|
|
|
2743
2960
|
}
|
|
2744
2961
|
});
|
|
2745
2962
|
}
|
|
2746
|
-
function
|
|
2963
|
+
function init8() {
|
|
2747
2964
|
disableOnClickBeforeSave();
|
|
2748
2965
|
enableOnClickForAdminOnPageLoad();
|
|
2749
2966
|
}
|
|
@@ -2780,14 +2997,14 @@
|
|
|
2780
2997
|
enableAdminResources();
|
|
2781
2998
|
});
|
|
2782
2999
|
}
|
|
2783
|
-
function enableAdminResources(
|
|
2784
|
-
const live = (
|
|
2785
|
-
|
|
3000
|
+
function enableAdminResources(root3 = document) {
|
|
3001
|
+
const live = (root3.ownerDocument || root3) === document;
|
|
3002
|
+
root3.querySelectorAll(SELECTOR_INERT).forEach((resource) => {
|
|
2786
3003
|
makeActive(resource);
|
|
2787
3004
|
if (live) resource.replaceWith(resource.cloneNode(true));
|
|
2788
3005
|
});
|
|
2789
3006
|
}
|
|
2790
|
-
function
|
|
3007
|
+
function init9() {
|
|
2791
3008
|
disableAdminResourcesBeforeSave();
|
|
2792
3009
|
enableAdminResourcesOnPageLoad();
|
|
2793
3010
|
}
|
|
@@ -2807,15 +3024,15 @@
|
|
|
2807
3024
|
var admin_attrs_exports = {};
|
|
2808
3025
|
__export(admin_attrs_exports, {
|
|
2809
3026
|
default: () => admin_attrs_default,
|
|
2810
|
-
init: () =>
|
|
3027
|
+
init: () => init10
|
|
2811
3028
|
});
|
|
2812
|
-
function
|
|
3029
|
+
function init10() {
|
|
2813
3030
|
if (initialized) return;
|
|
2814
3031
|
initialized = true;
|
|
2815
|
-
init5();
|
|
2816
3032
|
init6();
|
|
2817
3033
|
init7();
|
|
2818
3034
|
init8();
|
|
3035
|
+
init9();
|
|
2819
3036
|
}
|
|
2820
3037
|
var initialized, admin_attrs_default;
|
|
2821
3038
|
var init_admin_attrs = __esm({
|
|
@@ -2825,8 +3042,8 @@
|
|
|
2825
3042
|
init_admin_onclick();
|
|
2826
3043
|
init_admin_resources();
|
|
2827
3044
|
initialized = false;
|
|
2828
|
-
|
|
2829
|
-
admin_attrs_default =
|
|
3045
|
+
init10();
|
|
3046
|
+
admin_attrs_default = init10;
|
|
2830
3047
|
}
|
|
2831
3048
|
});
|
|
2832
3049
|
|
|
@@ -2855,7 +3072,7 @@
|
|
|
2855
3072
|
inputSaveTimer = setTimeout(savePageThrottled, 1500);
|
|
2856
3073
|
}, true);
|
|
2857
3074
|
}
|
|
2858
|
-
function
|
|
3075
|
+
function init11() {
|
|
2859
3076
|
if (!document.documentElement.matches(AUTOSAVE)) return;
|
|
2860
3077
|
if (!isEditMode) return;
|
|
2861
3078
|
initSavePageOnChange();
|
|
@@ -2873,8 +3090,8 @@
|
|
|
2873
3090
|
AUTOSAVE_DEBOUNCE_MS = 1500;
|
|
2874
3091
|
AUTOSAVE_MAX_WAIT_MS = 1e4;
|
|
2875
3092
|
inputSaveTimer = null;
|
|
2876
|
-
|
|
2877
|
-
autosave_default =
|
|
3093
|
+
init11();
|
|
3094
|
+
autosave_default = init11;
|
|
2878
3095
|
}
|
|
2879
3096
|
});
|
|
2880
3097
|
|
|
@@ -2927,7 +3144,7 @@
|
|
|
2927
3144
|
}
|
|
2928
3145
|
}
|
|
2929
3146
|
}
|
|
2930
|
-
function
|
|
3147
|
+
function init12() {
|
|
2931
3148
|
log("init, isEditMode:", isEditMode);
|
|
2932
3149
|
if (!isEditMode) return;
|
|
2933
3150
|
captureAll();
|
|
@@ -2952,7 +3169,7 @@
|
|
|
2952
3169
|
originals = /* @__PURE__ */ new WeakMap();
|
|
2953
3170
|
saveFreeze = { debug: false };
|
|
2954
3171
|
pending = /* @__PURE__ */ new WeakMap();
|
|
2955
|
-
|
|
3172
|
+
init12();
|
|
2956
3173
|
save_freeze_default = saveFreeze;
|
|
2957
3174
|
}
|
|
2958
3175
|
});
|
|
@@ -2975,14 +3192,14 @@
|
|
|
2975
3192
|
}
|
|
2976
3193
|
});
|
|
2977
3194
|
}
|
|
2978
|
-
function
|
|
3195
|
+
function init13() {
|
|
2979
3196
|
document.addEventListener("clay:save-saved", broadcast);
|
|
2980
3197
|
}
|
|
2981
3198
|
var onaftersave_default;
|
|
2982
3199
|
var init_onaftersave = __esm({
|
|
2983
3200
|
"src/attrs/onaftersave.js"() {
|
|
2984
|
-
|
|
2985
|
-
onaftersave_default =
|
|
3201
|
+
init13();
|
|
3202
|
+
onaftersave_default = init13;
|
|
2986
3203
|
}
|
|
2987
3204
|
});
|
|
2988
3205
|
|
|
@@ -3023,7 +3240,7 @@
|
|
|
3023
3240
|
mutation_default.resume();
|
|
3024
3241
|
}
|
|
3025
3242
|
}
|
|
3026
|
-
function
|
|
3243
|
+
function init14() {
|
|
3027
3244
|
document.addEventListener("clay:save-saved", () => {
|
|
3028
3245
|
document.querySelectorAll("[refetch-on-save]").forEach(swapElement);
|
|
3029
3246
|
});
|
|
@@ -3033,8 +3250,8 @@
|
|
|
3033
3250
|
"src/attrs/refetch-on-save.js"() {
|
|
3034
3251
|
init_mutation();
|
|
3035
3252
|
init_authored_url();
|
|
3036
|
-
|
|
3037
|
-
refetch_on_save_default =
|
|
3253
|
+
init14();
|
|
3254
|
+
refetch_on_save_default = init14;
|
|
3038
3255
|
}
|
|
3039
3256
|
});
|
|
3040
3257
|
|
|
@@ -6975,7 +7192,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
6975
7192
|
node.style.opacity = "0";
|
|
6976
7193
|
}, 2200);
|
|
6977
7194
|
}
|
|
6978
|
-
function
|
|
7195
|
+
function init15() {
|
|
6979
7196
|
if (!isEditMode) return;
|
|
6980
7197
|
for (const state of Object.keys(LABELS)) {
|
|
6981
7198
|
document.addEventListener("clay:save-" + state, () => show2(state));
|
|
@@ -6996,7 +7213,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
6996
7213
|
ALARMING = /* @__PURE__ */ new Set(["error", "offline"]);
|
|
6997
7214
|
el = null;
|
|
6998
7215
|
hideTimer = null;
|
|
6999
|
-
dom_ready_default(
|
|
7216
|
+
dom_ready_default(init15);
|
|
7000
7217
|
}
|
|
7001
7218
|
});
|
|
7002
7219
|
|
|
@@ -8426,8 +8643,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8426
8643
|
fillCloneIds(liveKids[i], cloneKids[i], liveWeakMap, idOf);
|
|
8427
8644
|
}
|
|
8428
8645
|
}
|
|
8429
|
-
function fillParsedIds(
|
|
8430
|
-
if (!
|
|
8646
|
+
function fillParsedIds(root3, identityMap, idOf) {
|
|
8647
|
+
if (!root3 || !identityMap) return;
|
|
8431
8648
|
const visit = (el3, path) => {
|
|
8432
8649
|
const id = identityMap[path];
|
|
8433
8650
|
if (id) idOf.set(el3, id);
|
|
@@ -8436,7 +8653,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8436
8653
|
visit(kids[i], path === "" ? String(i) : `${path}.${i}`);
|
|
8437
8654
|
}
|
|
8438
8655
|
};
|
|
8439
|
-
visit(
|
|
8656
|
+
visit(root3, "");
|
|
8440
8657
|
}
|
|
8441
8658
|
function protectPeerDoc({ newDoc, parsedWeakMap, baseHtml, baseIdentityMap, liveWeakMap }) {
|
|
8442
8659
|
if (typeof baseHtml !== "string" || !baseHtml) {
|
|
@@ -8596,6 +8813,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8596
8813
|
this.resumeId = null;
|
|
8597
8814
|
this.debounceMs = 150;
|
|
8598
8815
|
this.debounceTimer = null;
|
|
8816
|
+
this._savedSnapshot = null;
|
|
8599
8817
|
this._sendInFlight = false;
|
|
8600
8818
|
this._queuedSend = null;
|
|
8601
8819
|
this._profile = null;
|
|
@@ -8611,6 +8829,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8611
8829
|
this._pendingHtml = null;
|
|
8612
8830
|
this._pendingSeq = null;
|
|
8613
8831
|
this._pendingIdentityMap = null;
|
|
8832
|
+
this._pendingEtag = null;
|
|
8614
8833
|
this._morphInFlight = false;
|
|
8615
8834
|
this._rafHandle = null;
|
|
8616
8835
|
this._pendingExternal = null;
|
|
@@ -8681,6 +8900,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8681
8900
|
}
|
|
8682
8901
|
this.lastHtml = null;
|
|
8683
8902
|
this._lastIdentityMap = null;
|
|
8903
|
+
this._savedSnapshot = null;
|
|
8684
8904
|
this._applyGen++;
|
|
8685
8905
|
this.lastSeenSeq = 0;
|
|
8686
8906
|
this._lastExternalSeq = 0;
|
|
@@ -8696,6 +8916,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8696
8916
|
this.listenForSnapshots();
|
|
8697
8917
|
this._saveSavedHandler = () => {
|
|
8698
8918
|
this._saveEpoch++;
|
|
8919
|
+
this._relayCommit();
|
|
8699
8920
|
};
|
|
8700
8921
|
document.addEventListener("clay:save-saved", this._saveSavedHandler);
|
|
8701
8922
|
}
|
|
@@ -8785,8 +9006,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8785
9006
|
* Paths use the same dot-segment scheme as _buildIdentityMap so the
|
|
8786
9007
|
* receiver can look up IDs by the path the sender emitted.
|
|
8787
9008
|
*/
|
|
8788
|
-
_walkParsedTree(
|
|
8789
|
-
if (!
|
|
9009
|
+
_walkParsedTree(root3, cb) {
|
|
9010
|
+
if (!root3) return;
|
|
8790
9011
|
const visit = (el3, path) => {
|
|
8791
9012
|
cb(el3, path);
|
|
8792
9013
|
const kids = el3.children;
|
|
@@ -8794,7 +9015,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8794
9015
|
visit(kids[i], path === "" ? String(i) : `${path}.${i}`);
|
|
8795
9016
|
}
|
|
8796
9017
|
};
|
|
8797
|
-
visit(
|
|
9018
|
+
visit(root3, "");
|
|
8798
9019
|
}
|
|
8799
9020
|
/**
|
|
8800
9021
|
* Fill liveWeakMap entries for live elements that the matcher's
|
|
@@ -8900,31 +9121,32 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8900
9121
|
}));
|
|
8901
9122
|
}
|
|
8902
9123
|
/**
|
|
8903
|
-
*
|
|
9124
|
+
* Drop a stamp that arrived with no document (spec §6).
|
|
8904
9125
|
*
|
|
8905
|
-
*
|
|
8906
|
-
*
|
|
8907
|
-
*
|
|
9126
|
+
* ⚠️ This used to TAKE it, and taking it was wrong. A stamp says "disk is at this
|
|
9127
|
+
* version"; adopting it says "and I hold those bytes". Only the second claim
|
|
9128
|
+
* makes the next save safe, and a frame with no content is no evidence for it.
|
|
8908
9129
|
*
|
|
8909
|
-
*
|
|
8910
|
-
*
|
|
8911
|
-
*
|
|
9130
|
+
* The race is real and it loses work. A peer's save and that peer's snapshot
|
|
9131
|
+
* relay are two concurrent requests, so a stamp travelling on its own can arrive
|
|
9132
|
+
* first. This tab would record it while its DOM still held the older content,
|
|
9133
|
+
* and its next save would then pass If-Match and overwrite the save it had never
|
|
9134
|
+
* received. The hold flags cannot cover that: a lane holds when an incoming
|
|
9135
|
+
* change could NOT be merged, not when one has yet to arrive.
|
|
8912
9136
|
*
|
|
8913
|
-
*
|
|
8914
|
-
*
|
|
8915
|
-
*
|
|
8916
|
-
*
|
|
8917
|
-
*
|
|
8918
|
-
*
|
|
8919
|
-
* IS the overwrite. Refusing the stamp turns it into a conflict somebody is
|
|
8920
|
-
* told about.
|
|
9137
|
+
* So a stamp now rides on the snapshot the saving tab relays after its save
|
|
9138
|
+
* returns, and `_doApplyUpdate` records it only once that content has merged.
|
|
9139
|
+
* Nothing sends a bare stamp any more — hyperclay's was deleted, and it had
|
|
9140
|
+
* never reached anyone, because the relay library refuses a payload with no
|
|
9141
|
+
* document in it. This stays as the rule rather than as compatibility: if some
|
|
9142
|
+
* host does send one, dropping it is correct.
|
|
8921
9143
|
*
|
|
8922
9144
|
* @param {Object} data - The decoded frame
|
|
8923
|
-
* @returns {boolean} True when this was a stamp frame and nothing should morph
|
|
9145
|
+
* @returns {boolean} True when this was a stamp-only frame and nothing should morph
|
|
8924
9146
|
*/
|
|
8925
9147
|
_applyEtagFrame(data) {
|
|
8926
9148
|
if (typeof data.etag !== "string" || typeof data.html === "string") return false;
|
|
8927
|
-
|
|
9149
|
+
this._log("Dropping a stamp that arrived with no document: nothing proves this tab holds those bytes");
|
|
8928
9150
|
return true;
|
|
8929
9151
|
}
|
|
8930
9152
|
/**
|
|
@@ -8983,9 +9205,15 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
8983
9205
|
console.error("[LiveSync] Received invalid html, ignoring");
|
|
8984
9206
|
return;
|
|
8985
9207
|
}
|
|
9208
|
+
const etag = typeof data.etag === "string" && data.etag ? data.etag : null;
|
|
9209
|
+
if (etag && html === this.lastHtml) {
|
|
9210
|
+
this._log(`Taking the stamp from a peer save of content already applied (seq=${seq})`);
|
|
9211
|
+
recordEtag(etag);
|
|
9212
|
+
return;
|
|
9213
|
+
}
|
|
8986
9214
|
this._log(`Received update from: ${sender} (my clientId: ${this.clientId}, seq=${seq})`);
|
|
8987
|
-
this.applyUpdate(html, seq, identityMap);
|
|
8988
|
-
if (this.onUpdate) this.onUpdate({ html, sender, seq, identityMap });
|
|
9215
|
+
this.applyUpdate(html, seq, identityMap, etag);
|
|
9216
|
+
if (this.onUpdate) this.onUpdate({ html, sender, seq, identityMap, etag });
|
|
8989
9217
|
};
|
|
8990
9218
|
this.sse.onerror = () => {
|
|
8991
9219
|
if (this.sse.readyState === EventSource.CONNECTING) {
|
|
@@ -9012,6 +9240,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9012
9240
|
this._log("snapshot-ready received, preparing to send");
|
|
9013
9241
|
const html = serializeForSync(clone);
|
|
9014
9242
|
const identityMap = this._buildIdentityMap(document.documentElement, clone);
|
|
9243
|
+
this._savedSnapshot = { html, identityMap };
|
|
9015
9244
|
this.sendUpdate(html, identityMap);
|
|
9016
9245
|
};
|
|
9017
9246
|
document.addEventListener("clay:snapshot-ready", this._snapshotHandler);
|
|
@@ -9049,6 +9278,59 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9049
9278
|
this._queuedSend = null;
|
|
9050
9279
|
this._postUpdate(queued.html, queued.identityMap);
|
|
9051
9280
|
}
|
|
9281
|
+
/**
|
|
9282
|
+
* Tell the other editors what this tab's save stored, and under which stamp.
|
|
9283
|
+
*
|
|
9284
|
+
* Runs on clay:save-saved, by which point the save response has already been
|
|
9285
|
+
* recorded, so `lastSeenEtag()` is the stamp for the bytes that just landed.
|
|
9286
|
+
*
|
|
9287
|
+
* The content is `lastHtml`, the snapshot this tab most recently relayed, which
|
|
9288
|
+
* is the state the save was taken from: the save pipeline dispatches
|
|
9289
|
+
* clay:snapshot-ready on its way to capturing what to send, so that relay has
|
|
9290
|
+
* already gone out. Re-sending those same bytes is not redundant, because the
|
|
9291
|
+
* stamp is the new information and a stamp may never travel without the content
|
|
9292
|
+
* it describes. A receiver whose baseline already matches takes the stamp and
|
|
9293
|
+
* skips the morph.
|
|
9294
|
+
*/
|
|
9295
|
+
_relayCommit() {
|
|
9296
|
+
const etag = lastSeenEtag();
|
|
9297
|
+
if (!etag) return;
|
|
9298
|
+
if (this.isDestroyed || this.isPaused) return;
|
|
9299
|
+
const pending2 = this._savedSnapshot;
|
|
9300
|
+
this._savedSnapshot = null;
|
|
9301
|
+
if (!pending2 || typeof pending2.html !== "string") return;
|
|
9302
|
+
this._postCommit(pending2.html, etag, pending2.identityMap);
|
|
9303
|
+
}
|
|
9304
|
+
/**
|
|
9305
|
+
* Post a snapshot whose point is the stamp attached to it.
|
|
9306
|
+
*
|
|
9307
|
+
* Deliberately not `_postUpdate`: that one returns early when the html matches
|
|
9308
|
+
* `lastHtml`, and the whole point here is that the stamp is the new information
|
|
9309
|
+
* even when the bytes are not. It also does not touch `lastHtml` or the
|
|
9310
|
+
* single-flight queue, because it must not displace a real snapshot waiting to
|
|
9311
|
+
* go out. It carries the identityMap captured with these bytes, so a receiver
|
|
9312
|
+
* that morphs on this frame pairs elements the same way it would on the
|
|
9313
|
+
* ordinary relay of the same content.
|
|
9314
|
+
*/
|
|
9315
|
+
_postCommit(html, etag, identityMap) {
|
|
9316
|
+
const profile = this._profile;
|
|
9317
|
+
if (!profile) return;
|
|
9318
|
+
fetch(new URL(profile.relayPath, window.location.origin).href, {
|
|
9319
|
+
method: "POST",
|
|
9320
|
+
headers: {
|
|
9321
|
+
"Content-Type": "application/json",
|
|
9322
|
+
[profile.documentHeader]: window.location.href
|
|
9323
|
+
},
|
|
9324
|
+
body: JSON.stringify({
|
|
9325
|
+
[profile.snapshotKey]: html,
|
|
9326
|
+
sender: this.clientId,
|
|
9327
|
+
identityMap,
|
|
9328
|
+
etag
|
|
9329
|
+
})
|
|
9330
|
+
}).catch((err) => {
|
|
9331
|
+
this._log("Commit relay failed: " + (err && err.message));
|
|
9332
|
+
});
|
|
9333
|
+
}
|
|
9052
9334
|
_postUpdate(html, identityMap) {
|
|
9053
9335
|
if (html === this.lastHtml) {
|
|
9054
9336
|
this._log("Skipping send - HTML unchanged");
|
|
@@ -9105,7 +9387,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9105
9387
|
const info = data.data;
|
|
9106
9388
|
if (info && info.kind === "external-change") {
|
|
9107
9389
|
if (typeof info.html === "string") {
|
|
9108
|
-
this._enqueueExternal(info.html, data.seq);
|
|
9390
|
+
this._enqueueExternal(info.html, data.seq, info.etag);
|
|
9109
9391
|
} else {
|
|
9110
9392
|
this._fetchExternalChange(data.seq);
|
|
9111
9393
|
}
|
|
@@ -9117,7 +9399,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9117
9399
|
}
|
|
9118
9400
|
return false;
|
|
9119
9401
|
}
|
|
9120
|
-
_enqueueExternal(html, seq) {
|
|
9402
|
+
_enqueueExternal(html, seq, etag) {
|
|
9121
9403
|
if (typeof seq === "number") {
|
|
9122
9404
|
if (seq <= this._lastExternalSeq) {
|
|
9123
9405
|
this._log(`Dropping replayed external change: seq=${seq}`);
|
|
@@ -9125,7 +9407,12 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9125
9407
|
}
|
|
9126
9408
|
this._lastExternalSeq = seq;
|
|
9127
9409
|
}
|
|
9128
|
-
this._pendingExternal = {
|
|
9410
|
+
this._pendingExternal = {
|
|
9411
|
+
html,
|
|
9412
|
+
seq,
|
|
9413
|
+
saveEpoch: this._saveEpoch,
|
|
9414
|
+
etag: typeof etag === "string" && etag ? etag : null
|
|
9415
|
+
};
|
|
9129
9416
|
this._scheduleNextFrame();
|
|
9130
9417
|
}
|
|
9131
9418
|
/**
|
|
@@ -9165,7 +9452,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9165
9452
|
}
|
|
9166
9453
|
return;
|
|
9167
9454
|
}
|
|
9168
|
-
this._pendingExternal = { html, seq, saveEpoch: epoch };
|
|
9455
|
+
this._pendingExternal = { html, seq, saveEpoch: epoch, etag: null };
|
|
9169
9456
|
this._scheduleNextFrame();
|
|
9170
9457
|
}).catch((err) => {
|
|
9171
9458
|
this._log("External-change fetch failed", err);
|
|
@@ -9188,11 +9475,12 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9188
9475
|
* @param {number} [seq] - Optional monotonic seq from the server
|
|
9189
9476
|
* @param {Object} [identityMap] - Optional element-identity map from sender
|
|
9190
9477
|
*/
|
|
9191
|
-
applyUpdate(html, seq, identityMap) {
|
|
9478
|
+
applyUpdate(html, seq, identityMap, etag) {
|
|
9192
9479
|
if (this.isDestroyed) return;
|
|
9193
9480
|
this._pendingHtml = html;
|
|
9194
9481
|
this._pendingSeq = seq;
|
|
9195
9482
|
this._pendingIdentityMap = identityMap;
|
|
9483
|
+
this._pendingEtag = etag ?? null;
|
|
9196
9484
|
this._scheduleNextFrame();
|
|
9197
9485
|
}
|
|
9198
9486
|
/**
|
|
@@ -9236,7 +9524,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9236
9524
|
} else {
|
|
9237
9525
|
this._morphInFlight = true;
|
|
9238
9526
|
try {
|
|
9239
|
-
await this._doApplyExternal(ext.html, ext.seq);
|
|
9527
|
+
await this._doApplyExternal(ext.html, ext.seq, ext.etag);
|
|
9240
9528
|
} catch (err) {
|
|
9241
9529
|
console.error("[LiveSync] applyExternal failed:", err);
|
|
9242
9530
|
} finally {
|
|
@@ -9247,13 +9535,15 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9247
9535
|
const html = this._pendingHtml;
|
|
9248
9536
|
const seq = this._pendingSeq;
|
|
9249
9537
|
const identityMap = this._pendingIdentityMap;
|
|
9538
|
+
const etag = this._pendingEtag;
|
|
9250
9539
|
this._pendingHtml = null;
|
|
9251
9540
|
this._pendingSeq = null;
|
|
9252
9541
|
this._pendingIdentityMap = null;
|
|
9542
|
+
this._pendingEtag = null;
|
|
9253
9543
|
if (html == null) return;
|
|
9254
9544
|
this._morphInFlight = true;
|
|
9255
9545
|
try {
|
|
9256
|
-
await this._doApplyUpdate(html, seq, identityMap);
|
|
9546
|
+
await this._doApplyUpdate(html, seq, identityMap, etag);
|
|
9257
9547
|
} catch (err) {
|
|
9258
9548
|
console.error("[LiveSync] applyUpdate failed:", err);
|
|
9259
9549
|
} finally {
|
|
@@ -9285,7 +9575,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9285
9575
|
* @param {Object} [identityMap]
|
|
9286
9576
|
* @returns {Promise<void>}
|
|
9287
9577
|
*/
|
|
9288
|
-
async _doApplyUpdate(html, seq, identityMap) {
|
|
9578
|
+
async _doApplyUpdate(html, seq, identityMap, etag) {
|
|
9289
9579
|
this._log("applyUpdate - pausing mutations and morphing");
|
|
9290
9580
|
this.isPaused = true;
|
|
9291
9581
|
const scrollX = window.scrollX;
|
|
@@ -9364,6 +9654,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9364
9654
|
this.lastHtml = html;
|
|
9365
9655
|
this._lastIdentityMap = identityMap && typeof identityMap === "object" && !Array.isArray(identityMap) ? identityMap : null;
|
|
9366
9656
|
this._applyGen++;
|
|
9657
|
+
if (typeof etag === "string" && etag) recordEtag(etag);
|
|
9367
9658
|
if (this.lane === "live" && retainedRoots === 0 && !pageMaybeDirty()) {
|
|
9368
9659
|
setLastSavedBaselines(...pairedBaseline());
|
|
9369
9660
|
}
|
|
@@ -9396,7 +9687,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9396
9687
|
* holds or a later dirty peer apply misclassifies this frame's content as
|
|
9397
9688
|
* local edits.
|
|
9398
9689
|
*/
|
|
9399
|
-
async _doApplyExternal(html, seq) {
|
|
9690
|
+
async _doApplyExternal(html, seq, etag = null) {
|
|
9400
9691
|
this._log(`applyExternal - external disk change (seq=${seq})`);
|
|
9401
9692
|
this.isPaused = true;
|
|
9402
9693
|
const scrollX = window.scrollX;
|
|
@@ -9417,7 +9708,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9417
9708
|
this._holdRetryExt = setTimeout(() => {
|
|
9418
9709
|
this._holdRetryExt = null;
|
|
9419
9710
|
if (this.isDestroyed || this._pendingExternal != null) return;
|
|
9420
|
-
this._pendingExternal = { html, seq, saveEpoch: epochAtHold };
|
|
9711
|
+
this._pendingExternal = { html, seq, saveEpoch: epochAtHold, etag };
|
|
9421
9712
|
this._scheduleNextFrame();
|
|
9422
9713
|
}, 3e3);
|
|
9423
9714
|
return;
|
|
@@ -9443,6 +9734,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9443
9734
|
callbacks: { beforeAttributeUpdated }
|
|
9444
9735
|
});
|
|
9445
9736
|
window.scrollTo(scrollX, scrollY);
|
|
9737
|
+
if (typeof etag === "string" && etag) recordEtag(etag);
|
|
9446
9738
|
if (this.lane === "live" && retainedRoots === 0 && !pageMaybeDirty()) {
|
|
9447
9739
|
setLastSavedBaselines(...pairedBaseline());
|
|
9448
9740
|
setUnsavedChanges(false);
|
|
@@ -9452,7 +9744,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
9452
9744
|
this._applyGen++;
|
|
9453
9745
|
}
|
|
9454
9746
|
document.dispatchEvent(new CustomEvent("clay:sync-applied", {
|
|
9455
|
-
detail: { seq, source: "disk" }
|
|
9747
|
+
detail: { seq, source: "disk", etag: typeof etag === "string" && etag ? etag : null }
|
|
9456
9748
|
}));
|
|
9457
9749
|
} finally {
|
|
9458
9750
|
this._log("applyExternal - morph complete, resuming mutations");
|
|
@@ -10374,7 +10666,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
10374
10666
|
var sortable_exports = {};
|
|
10375
10667
|
__export(sortable_exports, {
|
|
10376
10668
|
default: () => sortable_default,
|
|
10377
|
-
init: () =>
|
|
10669
|
+
init: () => init16,
|
|
10378
10670
|
ready: () => ready
|
|
10379
10671
|
});
|
|
10380
10672
|
function makeSortable(sortableElem, Sortable) {
|
|
@@ -10425,7 +10717,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
10425
10717
|
};
|
|
10426
10718
|
Sortable.create(sortableElem, options);
|
|
10427
10719
|
}
|
|
10428
|
-
async function
|
|
10720
|
+
async function init16() {
|
|
10429
10721
|
if (!isEditMode) return;
|
|
10430
10722
|
const mod = await Promise.resolve().then(() => (init_Sortable_vendor(), Sortable_vendor_exports));
|
|
10431
10723
|
const Sortable = window.Sortable || mod.default;
|
|
@@ -10446,8 +10738,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
10446
10738
|
"src/plugins/sortable.js"() {
|
|
10447
10739
|
init_is_edit_mode();
|
|
10448
10740
|
init_mutation();
|
|
10449
|
-
ready =
|
|
10450
|
-
sortable_default =
|
|
10741
|
+
ready = init16();
|
|
10742
|
+
sortable_default = init16;
|
|
10451
10743
|
}
|
|
10452
10744
|
});
|
|
10453
10745
|
|
|
@@ -10870,7 +11162,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
10870
11162
|
default: () => undo_default,
|
|
10871
11163
|
undo: () => undo
|
|
10872
11164
|
});
|
|
10873
|
-
function
|
|
11165
|
+
function init17() {
|
|
10874
11166
|
if (!isEditMode) return;
|
|
10875
11167
|
if (typeof document === "undefined") return;
|
|
10876
11168
|
if (document.readyState === "loading") {
|
|
@@ -10884,7 +11176,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|
|
10884
11176
|
"src/plugins/undo.js"() {
|
|
10885
11177
|
init_hyper_undo_vendor();
|
|
10886
11178
|
init_is_edit_mode();
|
|
10887
|
-
|
|
11179
|
+
init17();
|
|
10888
11180
|
undo_default = undo;
|
|
10889
11181
|
}
|
|
10890
11182
|
});
|
|
@@ -17919,9 +18211,9 @@ body.hcms-open.hcms-overlay { overflow: hidden; }
|
|
|
17919
18211
|
})();
|
|
17920
18212
|
window.clay = window.clay || {};
|
|
17921
18213
|
window.clay.extractData = hyperHtmlApiData.extractData;
|
|
17922
|
-
window.clay.applyData = function(
|
|
17923
|
-
if (
|
|
17924
|
-
return hyperHtmlApiData.applyData(document,
|
|
18214
|
+
window.clay.applyData = function(root3, data, source) {
|
|
18215
|
+
if (root3 && typeof root3.nodeType === "number") return hyperHtmlApiData.applyData(root3, data, source);
|
|
18216
|
+
return hyperHtmlApiData.applyData(document, root3, data);
|
|
17925
18217
|
};
|
|
17926
18218
|
window.clay.loaded = window.clay.loaded || {};
|
|
17927
18219
|
window.clay.loaded.data = Promise.resolve();
|
|
@@ -19491,9 +19783,9 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
19491
19783
|
// src/dom/nearest.js
|
|
19492
19784
|
function nearest(startElem, selector, elementFoundReturnValue = (x) => x) {
|
|
19493
19785
|
const visited = /* @__PURE__ */ new Set();
|
|
19494
|
-
function checkDeep(
|
|
19495
|
-
if (!
|
|
19496
|
-
const queue = [
|
|
19786
|
+
function checkDeep(root3) {
|
|
19787
|
+
if (!root3 || visited.has(root3)) return null;
|
|
19788
|
+
const queue = [root3];
|
|
19497
19789
|
const localVisited = /* @__PURE__ */ new Set();
|
|
19498
19790
|
while (queue.length > 0) {
|
|
19499
19791
|
const node = queue.shift();
|
|
@@ -19551,9 +19843,9 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
19551
19843
|
var dom_helpers_exports = {};
|
|
19552
19844
|
__export(dom_helpers_exports, {
|
|
19553
19845
|
default: () => dom_helpers_default,
|
|
19554
|
-
init: () =>
|
|
19846
|
+
init: () => init18
|
|
19555
19847
|
});
|
|
19556
|
-
function
|
|
19848
|
+
function init18() {
|
|
19557
19849
|
if (HTMLElement.prototype.hasOwnProperty("nearest")) {
|
|
19558
19850
|
return;
|
|
19559
19851
|
}
|
|
@@ -19701,8 +19993,8 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
19701
19993
|
"src/dom/dom-helpers.js"() {
|
|
19702
19994
|
init_nearest();
|
|
19703
19995
|
init_pipe();
|
|
19704
|
-
|
|
19705
|
-
dom_helpers_default =
|
|
19996
|
+
init18();
|
|
19997
|
+
dom_helpers_default = init18;
|
|
19706
19998
|
}
|
|
19707
19999
|
});
|
|
19708
20000
|
|
|
@@ -20617,7 +20909,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
20617
20909
|
for (const id in triggerMap) validateModalPresence(id);
|
|
20618
20910
|
return true;
|
|
20619
20911
|
};
|
|
20620
|
-
const
|
|
20912
|
+
const init28 = (config) => {
|
|
20621
20913
|
const options = Object.assign({}, { openTrigger: "data-micromodal-trigger" }, config);
|
|
20622
20914
|
const triggers = [...document.querySelectorAll(`[${options.openTrigger}]`)];
|
|
20623
20915
|
const triggerMap = generateTriggerMap(triggers, options.openTrigger);
|
|
@@ -20642,7 +20934,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
20642
20934
|
targetModal ? activeModal.closeModalById(targetModal) : activeModal.closeModal();
|
|
20643
20935
|
activeModal = null;
|
|
20644
20936
|
};
|
|
20645
|
-
return { init:
|
|
20937
|
+
return { init: init28, show: show3, close };
|
|
20646
20938
|
})();
|
|
20647
20939
|
modalCss = `<style class="micromodal-css">
|
|
20648
20940
|
.micromodal {
|
|
@@ -20968,7 +21260,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
20968
21260
|
onNo = [];
|
|
20969
21261
|
onOpen = [];
|
|
20970
21262
|
let settled = false;
|
|
20971
|
-
const
|
|
21263
|
+
const dismiss2 = () => {
|
|
20972
21264
|
if (settled) return;
|
|
20973
21265
|
settled = true;
|
|
20974
21266
|
myOnNo.forEach((cb) => cb());
|
|
@@ -20980,7 +21272,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
20980
21272
|
this.isShowing = false;
|
|
20981
21273
|
document.body.style.overflow = "";
|
|
20982
21274
|
}
|
|
20983
|
-
this._dismiss =
|
|
21275
|
+
this._dismiss = dismiss2;
|
|
20984
21276
|
document.body.insertAdjacentHTML("afterbegin", "<div clay='no-save no-snapshot' class='micromodal-parent'>" + modalCss + modalHtml + "</div>");
|
|
20985
21277
|
const modalOverlayElem = document.querySelector(".micromodal__overlay");
|
|
20986
21278
|
const modalContentElem = document.querySelector(".micromodal__content");
|
|
@@ -21047,7 +21339,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21047
21339
|
// we use our own
|
|
21048
21340
|
// reset everything on close
|
|
21049
21341
|
onClose: (modal) => {
|
|
21050
|
-
|
|
21342
|
+
dismiss2();
|
|
21051
21343
|
document.querySelector(".micromodal-parent")?.remove();
|
|
21052
21344
|
html = "";
|
|
21053
21345
|
yes = "";
|
|
@@ -21281,7 +21573,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21281
21573
|
modal_default.open();
|
|
21282
21574
|
return promise;
|
|
21283
21575
|
}
|
|
21284
|
-
function
|
|
21576
|
+
function init19() {
|
|
21285
21577
|
dom_ready_default(() => {
|
|
21286
21578
|
const micromodalParentElem = document.querySelector(".micromodal-parent");
|
|
21287
21579
|
if (micromodalParentElem) {
|
|
@@ -21299,7 +21591,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21299
21591
|
init_copy_to_clipboard();
|
|
21300
21592
|
CLOSE_BUTTON_SVG = `<svg viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"><path class="micromodal__close-bg" d="M132 132.5 1 1.5h131v131Z" /><path class="micromodal__close-x" fill-rule="evenodd" clip-rule="evenodd" d="M0 0h3v1.5h1.5V3H6v1.5h1.5V6H9v1.5h1.5V9H12v1.5h1.5V12H15v1.5h1.5V15H18v1.5h1.5V18H21v1.5h1.5V21H24v1.5h1.5V24H27v1.5h1.5V27H30v1.5h1.5V30H33v1.5h1.5V33H36v1.5h1.5V36H39v1.5h1.5V39H42v1.5h1.5V42H45v1.5h1.5V45H48v1.5h1.5V48H51v1.5h1.5V51H54v1.5h1.5V54H57v1.5h1.5V57H60v1.5h1.5V60H63v1.5h1.5V63H66v1.5h1.5V66H69v1.5h1.5V69H72v1.5h1.5V72H75v1.5h1.5V75H78v1.5h1.5V78H81v1.5h1.5V81H84v1.5h1.5V84H87v1.5h1.5V87H90v1.5h1.5V90H93v1.5h1.5V93H96v1.5h1.5V96H99v1.5h1.5V99h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v3h-3V132H129v-1.5h-1.5V129H126v-1.5h-1.5V126H123v-1.5h-1.5V123H120v-1.5h-1.5V120H117v-1.5h-1.5V117H114v-1.5h-1.5V114H111v-1.5h-1.5V111H108v-1.5h-1.5V108H105v-1.5h-1.5V105H102v-1.5h-1.5V102H99v-1.5h-1.5V99H96v-1.5h-1.5V96H93v-1.5h-1.5V93H90v-1.5h-1.5V90H87v-1.5h-1.5V87H84v-1.5h-1.5V84H81v-1.5h-1.5V81H78v-1.5h-1.5V78H75v-1.5h-1.5V75H72v-1.5h-1.5V72H69v-1.5h-1.5V69H66v-1.5h-1.5V66H63v-1.5h-1.5V63H60v-1.5h-1.5V60H57v-1.5h-1.5V57H54v-1.5h-1.5V54H51v-1.5h-1.5V51H48v-1.5h-1.5V48H45v-1.5h-1.5V45H42v-1.5h-1.5V42H39v-1.5h-1.5V39H36v-1.5h-1.5V36H33v-1.5h-1.5V33H30v-1.5h-1.5V30H27v-1.5h-1.5V27H24v-1.5h-1.5V24H21v-1.5h-1.5V21H18v-1.5h-1.5V18H15v-1.5h-1.5V15H12v-1.5h-1.5V12H9v-1.5H7.5V9H6V7.5H4.5V6H3V4.5H1.5V3H0V0ZM108.8 22h5.2v5.1h-2.6v2.6H109v2.6h-2.6v2.6h-2.6v2.5h-2.6v5.2h2.6V45h2.6v2.6h2.6v2.6h2.5v2.6h2.6V58h-5.1v-2.6h-2.6V53h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6h-5.2v2.6H91v2.6h-2.6v2.6h-2.6v2.5h-2.6V58H78v-5.1h2.6v-2.6H83v-2.6h2.6v-2.6h2.6v-2.5h2.6v-5.2h-2.6V35h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6H78V22h5.2v2.6h2.5V27h2.6v2.6h2.6v2.6h2.5v2.6h5.2v-2.6h2.5v-2.6h2.6v-2.6h2.6v-2.5h2.5V22Z" /></svg>`;
|
|
21301
21593
|
CONFIRM_BUTTON_SVG = `<div style="width: 28px;"><svg viewBox="0 0 60 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M34.5 0.75H43.5V5.25H49V9.75H54.5V14.25H60V18.75H54.5V23.25H49V27.75H43.5V32.25H34.5V27.75H40V23.25H45.5V18.75H0V14.25H45.5V9.75H40V5.25H34.5V0.75Z" fill="white"/></svg></div>`;
|
|
21302
|
-
|
|
21594
|
+
init19();
|
|
21303
21595
|
}
|
|
21304
21596
|
});
|
|
21305
21597
|
|
|
@@ -21406,7 +21698,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21406
21698
|
var options_exports = {};
|
|
21407
21699
|
__export(options_exports, {
|
|
21408
21700
|
default: () => options_default,
|
|
21409
|
-
init: () =>
|
|
21701
|
+
init: () => init20,
|
|
21410
21702
|
parseOptionAttribute: () => parseOptionAttribute
|
|
21411
21703
|
});
|
|
21412
21704
|
function parseOptionAttribute(attrName, attrValue) {
|
|
@@ -21423,7 +21715,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21423
21715
|
const values = rawValue.split("|");
|
|
21424
21716
|
return { name, rawValue, values, kind: match.kind, prefix: match.prefix, negated: match.kind === "show-not" };
|
|
21425
21717
|
}
|
|
21426
|
-
function
|
|
21718
|
+
function init20() {
|
|
21427
21719
|
optionVisibility.start();
|
|
21428
21720
|
}
|
|
21429
21721
|
var STYLE_NAME, OPTION_PREFIXES, optionVisibility, options_default;
|
|
@@ -21557,12 +21849,12 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21557
21849
|
};
|
|
21558
21850
|
window.clay.optionVisibility = optionVisibility;
|
|
21559
21851
|
options_default = optionVisibility;
|
|
21560
|
-
|
|
21852
|
+
init20();
|
|
21561
21853
|
}
|
|
21562
21854
|
});
|
|
21563
21855
|
|
|
21564
21856
|
// src/events/onclickaway.js
|
|
21565
|
-
function
|
|
21857
|
+
function init21() {
|
|
21566
21858
|
document.addEventListener("click", function(event) {
|
|
21567
21859
|
const elementsWithOnClickAway = document.querySelectorAll("[onclickaway]");
|
|
21568
21860
|
elementsWithOnClickAway.forEach((element) => {
|
|
@@ -21583,7 +21875,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21583
21875
|
});
|
|
21584
21876
|
|
|
21585
21877
|
// src/events/onclickchildren.js
|
|
21586
|
-
function
|
|
21878
|
+
function init22() {
|
|
21587
21879
|
document.addEventListener("click", function(event) {
|
|
21588
21880
|
const parent = event.target.closest("[onclickchildren]");
|
|
21589
21881
|
if (parent && event.target !== parent) {
|
|
@@ -21598,7 +21890,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21598
21890
|
});
|
|
21599
21891
|
|
|
21600
21892
|
// src/events/onclone.js
|
|
21601
|
-
function
|
|
21893
|
+
function init23() {
|
|
21602
21894
|
if (Node.prototype.__hyperclayOnclone) {
|
|
21603
21895
|
return;
|
|
21604
21896
|
}
|
|
@@ -21651,7 +21943,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21651
21943
|
console.error("Error in onmutation execution:", error);
|
|
21652
21944
|
}
|
|
21653
21945
|
}
|
|
21654
|
-
function
|
|
21946
|
+
function init24() {
|
|
21655
21947
|
mutation_default.onAnyChange({ require: "observed" }, (changes2) => {
|
|
21656
21948
|
const toFire = /* @__PURE__ */ new Set();
|
|
21657
21949
|
for (const change of changes2) {
|
|
@@ -21671,7 +21963,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21671
21963
|
});
|
|
21672
21964
|
|
|
21673
21965
|
// src/events/onpagemutation.js
|
|
21674
|
-
function
|
|
21966
|
+
function init25() {
|
|
21675
21967
|
const executeGlobalMutation = async (element) => {
|
|
21676
21968
|
try {
|
|
21677
21969
|
const code = element.getAttribute("onglobalmutation") || element.getAttribute("onpagemutation");
|
|
@@ -21711,7 +22003,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21711
22003
|
});
|
|
21712
22004
|
|
|
21713
22005
|
// src/events/onrender.js
|
|
21714
|
-
function
|
|
22006
|
+
function init26() {
|
|
21715
22007
|
const executeRender = async (element) => {
|
|
21716
22008
|
try {
|
|
21717
22009
|
const code = element.getAttribute("onrender");
|
|
@@ -21744,15 +22036,15 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21744
22036
|
var events_exports = {};
|
|
21745
22037
|
__export(events_exports, {
|
|
21746
22038
|
default: () => events_default,
|
|
21747
|
-
init: () =>
|
|
22039
|
+
init: () => init27
|
|
21748
22040
|
});
|
|
21749
|
-
function
|
|
21750
|
-
init20();
|
|
22041
|
+
function init27() {
|
|
21751
22042
|
init21();
|
|
21752
22043
|
init22();
|
|
21753
22044
|
init23();
|
|
21754
22045
|
init24();
|
|
21755
22046
|
init25();
|
|
22047
|
+
init26();
|
|
21756
22048
|
}
|
|
21757
22049
|
var events_default;
|
|
21758
22050
|
var init_events = __esm({
|
|
@@ -21763,8 +22055,8 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21763
22055
|
init_onmutation();
|
|
21764
22056
|
init_onpagemutation();
|
|
21765
22057
|
init_onrender();
|
|
21766
|
-
|
|
21767
|
-
events_default =
|
|
22058
|
+
init27();
|
|
22059
|
+
events_default = init27;
|
|
21768
22060
|
}
|
|
21769
22061
|
});
|
|
21770
22062
|
|
|
@@ -21773,10 +22065,14 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21773
22065
|
always: [
|
|
21774
22066
|
"lib/mutation.js",
|
|
21775
22067
|
// first: everything hangs off the hub; cms/sync need it in view mode too
|
|
21776
|
-
"core/edit-mode.js"
|
|
22068
|
+
"core/edit-mode.js",
|
|
21777
22069
|
// both modes — matches today: 'edit-mode' is NOT in EDIT_MODE_ONLY
|
|
21778
22070
|
// (hyperclay.js:251-273 lists 'edit-mode-helpers', not 'edit-mode');
|
|
21779
22071
|
// toggleEditMode must exist in view mode, it's the way IN
|
|
22072
|
+
// always, deliberately: it speaks only when the host is too old to save to, which
|
|
22073
|
+
// is exactly the case where edit mode is off, so the editOnly wave would never
|
|
22074
|
+
// reach it. It draws nothing on any other page.
|
|
22075
|
+
"core/stale-host-notice.js"
|
|
21780
22076
|
],
|
|
21781
22077
|
editOnly: [
|
|
21782
22078
|
"core/snapshot.js",
|
|
@@ -21813,6 +22109,7 @@ ${t}` : `"use strict"; return (${t});`;
|
|
|
21813
22109
|
"lib/region-policy.js": () => Promise.resolve().then(() => (init_region_policy(), region_policy_exports)),
|
|
21814
22110
|
"lib/mutation.js": () => Promise.resolve().then(() => (init_mutation(), mutation_exports)),
|
|
21815
22111
|
"core/edit-mode.js": () => Promise.resolve().then(() => (init_edit_mode(), edit_mode_exports)),
|
|
22112
|
+
"core/stale-host-notice.js": () => Promise.resolve().then(() => (init_stale_host_notice(), stale_host_notice_exports)),
|
|
21816
22113
|
"core/snapshot.js": () => Promise.resolve().then(() => (init_snapshot(), snapshot_exports)),
|
|
21817
22114
|
"core/save-core.js": () => Promise.resolve().then(() => (init_save_core(), save_core_exports)),
|
|
21818
22115
|
"core/save.js": () => Promise.resolve().then(() => (init_save(), save_exports)),
|