@panphora/clayjs 1.5.3 → 1.6.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.
Files changed (45) hide show
  1. package/README.md +5 -3
  2. package/THIRD-PARTY-NOTICES.md +6 -0
  3. package/dist/clay.standalone.js +14965 -10714
  4. package/package.json +9 -4
  5. package/src/core/conflict-download.js +91 -0
  6. package/src/core/conflict-notice.js +720 -0
  7. package/src/core/conflict-presentation.js +301 -0
  8. package/src/core/save-conflict-notice.js +2 -192
  9. package/src/core/save-feedback.js +6 -0
  10. package/src/core/save.js +53 -1
  11. package/src/core/snapshot.js +21 -0
  12. package/src/core/stale-host-notice.js +21 -24
  13. package/src/core/unsaved-warning.js +35 -10
  14. package/src/lib/content-dom.js +4 -2
  15. package/src/lib/dirty-gate.js +9 -0
  16. package/src/lib/unsaved-state.js +55 -0
  17. package/src/loader-logic.js +26 -15
  18. package/src/loader.js +4 -3
  19. package/src/plugins/ai-edit.js +489 -179
  20. package/src/plugins/cms.js +12 -0
  21. package/src/plugins/indicator.js +35 -16
  22. package/src/plugins/quickcrop.js +103 -0
  23. package/src/plugins/richclay.js +21 -0
  24. package/src/plugins/source.js +13 -6
  25. package/src/plugins/wire.js +4 -0
  26. package/src/sync/conflict-revert.js +1138 -0
  27. package/src/sync/conflicts.js +159 -0
  28. package/src/sync/live-sync.js +130 -109
  29. package/src/sync/presence.js +42 -36
  30. package/src/sync/section-notice.js +53 -28
  31. package/src/ui/bevel-controls.js +277 -0
  32. package/src/ui/bevel-dialog.js +112 -0
  33. package/src/ui/bevel.js +247 -0
  34. package/src/ui/dialogs.js +63 -86
  35. package/src/ui/index.js +22 -11
  36. package/src/ui/modal.js +80 -387
  37. package/src/ui/skins/cms-toggle.js +15 -0
  38. package/src/ui/skins/cms.js +4 -0
  39. package/src/ui/skins/quickcrop.js +4 -0
  40. package/src/ui/skins/richclay.js +4 -0
  41. package/src/ui/toast.js +116 -401
  42. package/src/ui/vendor-skin.js +65 -0
  43. package/src/vendor/hyper-morph.vendor.js +4 -4
  44. package/src/vendor/quickcrop.vendor.js +14 -8
  45. package/src/vendor/richclay.vendor.js +22 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panphora/clayjs",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "description": "clayjs: malleable HTML files. Save lifecycle for self-saving HTML.",
6
6
  "license": "MIT-0",
@@ -54,11 +54,15 @@
54
54
  "test:standalone": "node build.js --standalone && web-test-runner --config tests/browser/wtr.config.mjs",
55
55
  "prepublishOnly": "npm test && npm run test:conformance && npm run test:standalone",
56
56
  "dev": "node tests/stub-server.mjs",
57
- "build": "node build.js",
57
+ "build": "npm run build:bevel && npm run build:skins && node build.js",
58
58
  "propagate:foundation": "node scripts/propagate-foundation.mjs",
59
59
  "check:foundation": "node scripts/propagate-foundation.mjs --check",
60
60
  "deploy": "node build.js && wrangler deploy && git rev-parse HEAD > .deploy",
61
- "build:parse5": "node scripts/build-parse5-vendor.mjs"
61
+ "build:parse5": "node scripts/build-parse5-vendor.mjs",
62
+ "build:bevel": "node scripts/build-bevel-subset.mjs",
63
+ "check:bevel": "node scripts/build-bevel-subset.mjs --check",
64
+ "build:skins": "node scripts/build-vendor-skins.mjs",
65
+ "check:skins": "node scripts/build-vendor-skins.mjs --check"
62
66
  },
63
67
  "devDependencies": {
64
68
  "@esm-bundle/chai": "^4.3.4-fix.0",
@@ -69,7 +73,8 @@
69
73
  "jest": "^29.7.0",
70
74
  "jest-environment-jsdom": "^29.7.0",
71
75
  "parse5": "^7.3.0",
72
- "playwright": "1.62.1"
76
+ "playwright": "1.62.1",
77
+ "postcss": "^8.5.28"
73
78
  },
74
79
  "hyper": {
75
80
  "kind": "lib",
@@ -0,0 +1,91 @@
1
+ import { serializeRecoveryClone, captureRecoveryPage } from "./snapshot.js";
2
+ import { rowOf } from "./conflict-presentation.js";
3
+
4
+ // Download my copy: one file, openable in a browser as the page this tab had, with
5
+ // the replaced edits listed in a JSON block for anyone (or any tool) putting them
6
+ // back by hand. Several applies lost edits: the newest one's page is the document,
7
+ // the older pages ride along as strings. Nothing here saves, acknowledges or
8
+ // releases anything.
9
+
10
+ function stamp(d = new Date()) {
11
+ const p = (n) => String(n).padStart(2, "0");
12
+ return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
13
+ }
14
+
15
+ export function documentName(pathname = location.pathname) {
16
+ const last = pathname.split("/").filter(Boolean).pop();
17
+ if (!last) return pathname.endsWith("/") || pathname === "" ? "index" : "page";
18
+ let name = last;
19
+ try { name = decodeURIComponent(last); } catch {}
20
+ return name.replace(/\.(html?|htmlclay)$/i, "") || "page";
21
+ }
22
+
23
+ function embed(html, data) {
24
+ const json = JSON.stringify(data).replace(/</g, "\\u003c");
25
+ const block = `<script type="application/json" id="clay-lost-edits" clay="no-save">${json}</script>`;
26
+ const at = html.lastIndexOf("</body>");
27
+ return at >= 0 ? html.slice(0, at) + block + html.slice(at) : html + block;
28
+ }
29
+
30
+ export function buildRecovery(records, { refused = false } = {}) {
31
+ const ledger = window.clay?.conflicts || null;
32
+ const applies = [];
33
+ const seen = new Set();
34
+ for (const r of records) {
35
+ const apply = ledger?.recoveryOf(r.id);
36
+ if (apply && !seen.has(apply.id)) { seen.add(apply.id); applies.push(apply); }
37
+ }
38
+ applies.sort((a, b) => (a.ticket || 0) - (b.ticket || 0));
39
+ const pageOf = (a) => serializeRecoveryClone(a.root, { prepared: a.domain === "save" });
40
+
41
+ let html;
42
+ let older;
43
+ if (refused || !applies.length) {
44
+ html = captureRecoveryPage();
45
+ older = applies;
46
+ } else {
47
+ html = pageOf(applies[applies.length - 1]);
48
+ older = applies.slice(0, -1);
49
+ }
50
+ const edits = records.map((r) => {
51
+ if (r.kind === "apply-incomplete") return { id: r.id, kind: "apply-incomplete", name: "Sync update that did not finish", error: r.error ?? null };
52
+ const { id, name, kind, yours, now, sentence } = rowOf(r);
53
+ return { id, name, kind, yours, now, sentence };
54
+ });
55
+ const data = {
56
+ format: "clay-lost-edits",
57
+ version: 1,
58
+ savedAt: new Date().toISOString(),
59
+ page: location.href,
60
+ refused: !!refused,
61
+ edits,
62
+ earlierPages: older.map((a) => ({ applyId: a.id, source: a.source, html: pageOf(a) })),
63
+ };
64
+ return {
65
+ html: embed(html, data),
66
+ name: `${documentName()}-${refused ? "my-version" : "my-copy"}-${stamp()}.html`,
67
+ };
68
+ }
69
+
70
+ export function downloadRecovery(records, { refused = false } = {}) {
71
+ let url = null;
72
+ try {
73
+ const { html, name } = buildRecovery(records, { refused });
74
+ url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
75
+ const a = document.createElement("a");
76
+ a.setAttribute("clay", "no-save no-watch no-snapshot");
77
+ a.href = url;
78
+ a.download = name;
79
+ a.style.setProperty("display", "none", "important");
80
+ document.body.appendChild(a);
81
+ a.click();
82
+ a.remove();
83
+ // Revoked late: Safari cancels a download whose URL goes away too soon.
84
+ setTimeout(() => URL.revokeObjectURL(url), 40000);
85
+ return true;
86
+ } catch (err) {
87
+ console.error("clayjs: could not build the download", err);
88
+ if (url) URL.revokeObjectURL(url);
89
+ return false;
90
+ }
91
+ }