@panphora/clayjs 0.2.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +103 -7
  2. package/clay-data.js +1 -1
  3. package/clay-internals.js +19 -0
  4. package/clay.js +19 -3
  5. package/package.json +5 -2
  6. package/sap.js +5 -5
  7. package/src/attrs/onaftersave.js +12 -1
  8. package/src/attrs/refetch-on-save.js +4 -4
  9. package/src/attrs/save-freeze.js +47 -19
  10. package/src/core/admin-contenteditable.js +2 -2
  11. package/src/core/admin-inputs.js +2 -2
  12. package/src/core/admin-onclick.js +2 -2
  13. package/src/core/admin-resources.js +23 -14
  14. package/src/core/autosave.js +11 -1
  15. package/src/core/edit-mode.js +2 -2
  16. package/src/core/host-attrs.js +46 -0
  17. package/src/core/is-edit-mode.js +14 -6
  18. package/src/core/persist.js +12 -2
  19. package/src/core/save-core.js +199 -290
  20. package/src/core/save.js +121 -82
  21. package/src/core/snapshot.js +89 -36
  22. package/src/core/unsaved-warning.js +8 -2
  23. package/src/dom/all.js +4 -29
  24. package/src/dom/form-data.js +1 -1
  25. package/src/dom/nearest.js +11 -9
  26. package/src/internals/index.js +69 -0
  27. package/src/lib/cache-bust.js +10 -0
  28. package/src/lib/cookie.js +50 -15
  29. package/src/lib/extension-noise.js +5 -1
  30. package/src/lib/mutation.js +65 -18
  31. package/src/lib/region-policy.js +17 -2
  32. package/src/lib/root-attrs.js +45 -0
  33. package/src/lib/throttle.js +25 -13
  34. package/src/loader.js +18 -24
  35. package/src/options/options.js +1 -3
  36. package/src/plugins/demo.js +7 -1
  37. package/src/plugins/undo.js +3 -3
  38. package/src/sync/live-sync.js +77 -76
  39. package/src/ui/dialogs.js +10 -1
  40. package/src/ui/index.js +0 -10
  41. package/src/utils/debounce.js +24 -7
  42. package/src/vendor/control-serialize.vendor.js +2 -1
  43. package/src/vendor/hyper-undo.vendor.js +1 -1
  44. package/src/vendor/hypercms.vendor.js +29 -16
  45. package/src/vendor/richclay.vendor.js +29 -22
@@ -0,0 +1,46 @@
1
+ /**
2
+ * host-attrs.js — the attributes a host puts on <html>, and what they mean.
3
+ *
4
+ * These are ephemeral: the host injects them into the response and strips them
5
+ * back out of whatever the client saves, so they never reach disk. clayjs only
6
+ * ever reads them, and reads them here so the two spellings of the save token
7
+ * cannot drift between the edit-mode ladder and the save lane.
8
+ */
9
+
10
+ import { HOST_TOKEN_ATTRS, SAVE_TRANSPORT_ATTR } from "../lib/root-attrs.js";
11
+
12
+ // A host that wants the desktop JSON envelope on its save lane declares it on
13
+ // the root. This replaced a `location.hostname === 'localhost'` sniff, which sent
14
+ // the envelope to every host that happened to be local, including ones whose
15
+ // save lane takes text and answers 415.
16
+ export const DESKTOP_JSON = "desktop-json-v1";
17
+
18
+ /**
19
+ * The per-document save token this response carries, or null.
20
+ * @returns {?string}
21
+ */
22
+ export function saveToken() {
23
+ if (typeof document === "undefined") return null;
24
+ for (const attr of HOST_TOKEN_ATTRS) {
25
+ const value = document.documentElement.getAttribute(attr);
26
+ if (value) return value;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * True when the host handed this response a save token of either spelling.
33
+ * @returns {boolean}
34
+ */
35
+ export function hasSaveToken() {
36
+ return saveToken() !== null;
37
+ }
38
+
39
+ /**
40
+ * The save transport the served document declares, or null.
41
+ * @returns {?string}
42
+ */
43
+ export function saveTransport() {
44
+ if (typeof document === "undefined") return null;
45
+ return document.documentElement.getAttribute(SAVE_TRANSPORT_ATTR);
46
+ }
@@ -1,12 +1,20 @@
1
1
  import cookie from "../lib/cookie.js";
2
2
  import query from "../lib/query.js";
3
+ import { hasSaveToken } from "./host-attrs.js";
3
4
 
4
5
  // Edit-mode precedence: an explicit ?editmode=true|false URL param wins, then an
5
6
  // opt-in window.clayEditMode global (with the legacy window.__hyperclayEditMode
6
- // still honored as a fallback — htmlclay injects it today), then the platform's
7
- // isAdminOfCurrentResource cookie. The global is for standalone uses (demos,
8
- // htmlclay, any self-saving file) that are always editable and have no owner
9
- // cookie; setting it before clayjs loads turns on the edit-only modules.
7
+ // still honored as a fallback — htmlclay injects it today), then a save token the
8
+ // host put on the root, then the platform's isAdminOfCurrentResource cookie. The
9
+ // global is for standalone uses (demos, htmlclay, any self-saving file) that are
10
+ // always editable and have no owner cookie; setting it before clayjs loads turns
11
+ // on the edit-only modules.
12
+ //
13
+ // The token sits above the cookie because it is the stronger claim and often the
14
+ // only one visible: the host minted it for this response and nothing else, while
15
+ // the cookie is ambient. A sandboxed document cannot read the cookie at all, so
16
+ // without this rung a host that sandboxes its own documents can hand one a save
17
+ // token and still watch it render read-only.
10
18
  let forcedEditMode = null;
11
19
  if (typeof window !== "undefined") {
12
20
  if (window.clayEditMode != null) {
@@ -17,10 +25,10 @@ if (typeof window !== "undefined") {
17
25
  }
18
26
 
19
27
  const isEditMode = query.editmode
20
- ? query.editmode === "true" // takes precedence over the global and cookie
28
+ ? query.editmode === "true" // takes precedence over the global, token and cookie
21
29
  : forcedEditMode != null
22
30
  ? forcedEditMode
23
- : Boolean(cookie.get("isAdminOfCurrentResource"));
31
+ : hasSaveToken() || Boolean(cookie.get("isAdminOfCurrentResource"));
24
32
 
25
33
  const isOwner = Boolean(cookie.get("isAdminOfCurrentResource"));
26
34
 
@@ -10,7 +10,10 @@ import { serializeControlToAttributes, finalizeControlForSave } from '../vendor/
10
10
  //
11
11
  // The per-control rules (which attribute represents which control's state) live
12
12
  // in control-serialize.vendor.js — the single source of truth shared verbatim
13
- // with sapjs (carrier mirror) so the two implementations never drift. This
13
+ // with sapjs (carrier mirror) so the two implementations never drift. sapjs
14
+ // `npm run propagate` writes this copy and hyperclayjs's from one destination
15
+ // table, and `--check` fails on either being stale, so the invariant is
16
+ // enforced rather than remembered. This
14
17
  // module owns only the triggers (which elements, on which events) and the
15
18
  // snapshot safety net; serializeControlToAttributes / finalizeControlForSave do
16
19
  // the actual attribute writes.
@@ -86,7 +89,14 @@ export default function enablePersistentFormInputValues(filterBySelector = "[per
86
89
  const finalize = (selector) => {
87
90
  const live = document.querySelectorAll(selector);
88
91
  const cloned = doc.querySelectorAll(selector);
89
- cloned.forEach((c, i) => { if (live[i]) finalizeControlForSave(c, live[i]); });
92
+ // Index pairing only holds while the two lists describe the same tree. If a
93
+ // hook ever diverges them, writing one control's value into another's is
94
+ // worse than writing none, so fail loudly instead of silently.
95
+ if (live.length !== cloned.length) {
96
+ console.warn('[persist] live/clone counts differ for', selector, '— skipping');
97
+ return;
98
+ }
99
+ cloned.forEach((c, i) => finalizeControlForSave(c, live[i]));
90
100
  };
91
101
  finalize(inputSelector);
92
102
  finalize(textareaSelector);