@panphora/clayjs 1.5.3 → 1.6.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 (46) hide show
  1. package/README.md +5 -3
  2. package/THIRD-PARTY-NOTICES.md +6 -0
  3. package/dist/clay.standalone.js +15041 -10657
  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-core.js +35 -2
  10. package/src/core/save-feedback.js +6 -0
  11. package/src/core/save.js +157 -3
  12. package/src/core/snapshot.js +21 -0
  13. package/src/core/stale-host-notice.js +21 -24
  14. package/src/core/unsaved-warning.js +64 -16
  15. package/src/lib/content-dom.js +4 -2
  16. package/src/lib/dirty-gate.js +37 -1
  17. package/src/lib/unsaved-state.js +55 -0
  18. package/src/loader-logic.js +26 -15
  19. package/src/loader.js +10 -3
  20. package/src/plugins/ai-edit.js +489 -179
  21. package/src/plugins/cms.js +12 -0
  22. package/src/plugins/indicator.js +35 -16
  23. package/src/plugins/quickcrop.js +103 -0
  24. package/src/plugins/richclay.js +21 -0
  25. package/src/plugins/source.js +13 -6
  26. package/src/plugins/wire.js +4 -0
  27. package/src/sync/conflict-revert.js +1138 -0
  28. package/src/sync/conflicts.js +159 -0
  29. package/src/sync/live-sync.js +130 -109
  30. package/src/sync/presence.js +42 -36
  31. package/src/sync/section-notice.js +53 -28
  32. package/src/ui/bevel-controls.js +277 -0
  33. package/src/ui/bevel-dialog.js +112 -0
  34. package/src/ui/bevel.js +247 -0
  35. package/src/ui/dialogs.js +63 -86
  36. package/src/ui/index.js +22 -11
  37. package/src/ui/modal.js +80 -387
  38. package/src/ui/skins/cms-toggle.js +15 -0
  39. package/src/ui/skins/cms.js +4 -0
  40. package/src/ui/skins/quickcrop.js +4 -0
  41. package/src/ui/skins/richclay.js +4 -0
  42. package/src/ui/toast.js +116 -401
  43. package/src/ui/vendor-skin.js +65 -0
  44. package/src/vendor/hyper-morph.vendor.js +4 -4
  45. package/src/vendor/quickcrop.vendor.js +14 -8
  46. package/src/vendor/richclay.vendor.js +22 -41
@@ -16,7 +16,7 @@ export const CORE_WAVES = {
16
16
  // transform registered after that made an untouched page read as unsaved.
17
17
  "core/persist.js", "core/admin-attrs.js",
18
18
  "core/save-core.js", "core/save.js",
19
- "core/save-conflict-notice.js",
19
+ "core/conflict-notice.js",
20
20
  "core/unsaved-warning.js", "core/autosave.js",
21
21
  "attrs/save-freeze.js", "attrs/onaftersave.js", "attrs/refetch-on-save.js",
22
22
  "lib/cache-bust.js",
@@ -24,20 +24,21 @@ export const CORE_WAVES = {
24
24
  };
25
25
 
26
26
  export const PLUGIN_PATHS = {
27
- richclay: { path: "vendor/richclay.vendor.js", editOnly: true, default: true },
27
+ richclay: { path: "plugins/richclay.js", editOnly: true, default: true },
28
28
  indicator: { path: "plugins/indicator.js", editOnly: true, default: false },
29
29
  sync: { path: "sync/live-sync.js", editOnly: false, default: false },
30
30
  sortable: { path: "plugins/sortable.js", editOnly: true, default: false },
31
31
  undo: { path: "plugins/undo.js", editOnly: true, default: false },
32
- cms: { path: "vendor/hypercms.vendor.js", editOnly: false, default: false },
33
- quickcrop: { path: "vendor/quickcrop.vendor.js", editOnly: false, default: false },
32
+ cms: { path: "plugins/cms.js", editOnly: false, default: false },
33
+ quickcrop: { path: "plugins/quickcrop.js", editOnly: false, default: false },
34
34
  // editOnly, because a file picker only ever appears in edit mode: the cms
35
35
  // injects its own editing toggle there and clayjs's edit-mode signal is a
36
36
  // superset of the cms's, so the plugin is present exactly when it can be used.
37
37
  upload: { path: "plugins/upload.js", editOnly: true, default: false },
38
38
  wire: { path: "plugins/wire.js", editOnly: false, default: false },
39
- // Edit mode only: the AI comment box is an editing gesture, and the plugin is
40
- // dormant anyway on any host that does not list a ready `ai-edit` helper.
39
+ // Edit mode only: the AI comment box is an editing gesture. Opt in with
40
+ // `plugins=ai-edit`; it stays dormant on any host that does not list a ready
41
+ // `ai-edit` helper.
41
42
  "ai-edit": { path: "plugins/ai-edit.js", editOnly: true, default: false },
42
43
  demo: { path: "plugins/demo.js", editOnly: false, default: false },
43
44
  // Saves the file's own bytes back instead of a fresh serialization of the DOM.
@@ -68,7 +69,7 @@ export const MODULES = {
68
69
  "core/snapshot.js": () => import("./core/snapshot.js"),
69
70
  "core/save-core.js": () => import("./core/save-core.js"),
70
71
  "core/save.js": () => import("./core/save.js"),
71
- "core/save-conflict-notice.js": () => import("./core/save-conflict-notice.js"),
72
+ "core/conflict-notice.js": () => import("./core/conflict-notice.js"),
72
73
  "core/unsaved-warning.js": () => import("./core/unsaved-warning.js"),
73
74
  "core/persist.js": () => import("./core/persist.js"),
74
75
  "core/admin-attrs.js": () => import("./core/admin-attrs.js"),
@@ -78,13 +79,13 @@ export const MODULES = {
78
79
  "attrs/refetch-on-save.js": () => import("./attrs/refetch-on-save.js"),
79
80
  "lib/cache-bust.js": () => import("./lib/cache-bust.js"),
80
81
  "vendor/hyper-morph.vendor.js": () => import("./vendor/hyper-morph.vendor.js"),
81
- "vendor/richclay.vendor.js": () => import("./vendor/richclay.vendor.js"),
82
+ "plugins/richclay.js": () => import("./plugins/richclay.js"),
82
83
  "plugins/indicator.js": () => import("./plugins/indicator.js"),
83
84
  "sync/live-sync.js": () => import("./sync/live-sync.js"),
84
85
  "plugins/sortable.js": () => import("./plugins/sortable.js"),
85
86
  "plugins/undo.js": () => import("./plugins/undo.js"),
86
- "vendor/hypercms.vendor.js": () => import("./vendor/hypercms.vendor.js"),
87
- "vendor/quickcrop.vendor.js": () => import("./vendor/quickcrop.vendor.js"),
87
+ "plugins/cms.js": () => import("./plugins/cms.js"),
88
+ "plugins/quickcrop.js": () => import("./plugins/quickcrop.js"),
88
89
  "plugins/upload.js": () => import("./plugins/upload.js"),
89
90
  "plugins/wire.js": () => import("./plugins/wire.js"),
90
91
  "plugins/ai-edit.js": () => import("./plugins/ai-edit.js"),
@@ -114,6 +115,8 @@ const PLUGIN_ORDER = ["richclay", "indicator", "sortable", "undo", "quickcrop",
114
115
  // enables. Reverting it is reverting this line.
115
116
  // ai-edit reads `clay.wire.helpers()` and sends through `clay.wire.send`, so a page
116
117
  // that asks for it has to have the wire. The wire loads first in the order above.
118
+ // It does not bring undo: hyper-undo takes Cmd+Z for the whole window, text fields
119
+ // included, which a page that never asked for it should not get by default.
117
120
  const IMPLIES = { cms: ["quickcrop", "upload"], "ai-edit": ["wire"] };
118
121
 
119
122
  function parseCsv(params, key, enabled, apply) {
@@ -134,17 +137,25 @@ export function resolveModules(params, isEditMode) {
134
137
  const core = [...CORE_WAVES.always];
135
138
  if (isEditMode) core.push(...CORE_WAVES.editOnly);
136
139
 
137
- const enabled = new Set();
140
+ const explicit = new Set();
141
+ parseCsv(params, "plugins", explicit, (set, name) => set.add(name));
142
+ const excluded = new Set();
143
+ parseCsv(params, "exclude", excluded, (set, name) => set.add(name));
144
+
145
+ const enabled = new Set(explicit);
138
146
  for (const [name, spec] of Object.entries(PLUGIN_PATHS)) {
139
147
  if (spec.default) enabled.add(name);
140
148
  }
141
- parseCsv(params, "plugins", enabled, (set, name) => set.add(name));
142
- // Between the two: exclude still wins, so `plugins=cms&exclude=quickcrop` opts
143
- // back out of the cropper.
149
+ // ai-edit cannot run without the wire, so excluding the wire excludes it too.
150
+ if (excluded.has("wire")) enabled.delete("ai-edit");
151
+ // A default-on plugin that this mode drops brings nothing with it. An explicitly
152
+ // asked for plugin still brings what it implies.
144
153
  for (const name of [...enabled]) {
154
+ if (excluded.has(name)) continue;
155
+ if (!explicit.has(name) && PLUGIN_PATHS[name].editOnly && !isEditMode) continue;
145
156
  for (const implied of IMPLIES[name] || []) enabled.add(implied);
146
157
  }
147
- parseCsv(params, "exclude", enabled, (set, name) => set.delete(name));
158
+ for (const name of excluded) enabled.delete(name);
148
159
 
149
160
  const plugins = [];
150
161
  for (const name of PLUGIN_ORDER) {
package/src/loader.js CHANGED
@@ -88,8 +88,14 @@ function assembleCore(loaded, { isEditMode, isOwner }, regionPolicy) {
88
88
  const saveFn = save.savePage || save.default;
89
89
  saveFn.force = save.savePageForce;
90
90
  saveFn.overwrite = save.saveOverwritingConflict;
91
+ saveFn.flush = save.flushSave;
91
92
  clay.save = saveFn;
92
93
  }
94
+ // The close warning's own predicate, reachable without a capture. It falls back to
95
+ // false rather than to absent: the module is edit-only, and a document that cannot
96
+ // save has nothing unsaved. Older clayjs has no such member, and that is the answer
97
+ // a host feature-detects.
98
+ clay.hasUnsavedChanges = loaded["core/unsaved-warning.js"]?.hasUnsavedChanges ?? (() => false);
93
99
  if (snapshot) {
94
100
  clay.getHTML = snapshot.getPageContents;
95
101
  clay.addDocumentTransform = snapshot.addDocumentTransform;
@@ -108,7 +114,8 @@ function attachPluginMember(path, mod) {
108
114
  clay.undo = mod.undo || mod.default;
109
115
  } else if (path === "sync/live-sync.js") {
110
116
  clay.morph = mod.morph;
111
- } else if (path === "vendor/hypercms.vendor.js") {
117
+ clay.conflicts = mod.conflicts;
118
+ } else if (path === "plugins/cms.js") {
112
119
  clay.cms = mod.cms || mod.default;
113
120
  } else if (path === "plugins/upload.js") {
114
121
  clay.upload = mod.upload || mod.default;
@@ -118,9 +125,9 @@ function attachPluginMember(path, mod) {
118
125
  clay.aiEdit = mod.aiEdit || mod.default;
119
126
  } else if (path === "plugins/demo.js") {
120
127
  clay.demo = mod.demo;
121
- } else if (path === "vendor/richclay.vendor.js") {
128
+ } else if (path === "plugins/richclay.js") {
122
129
  clay.RichClay = mod.RichClay || mod.default;
123
- } else if (path === "vendor/quickcrop.vendor.js") {
130
+ } else if (path === "plugins/quickcrop.js") {
124
131
  clay.quickcrop = mod.quickcrop || mod.default;
125
132
  } else if (path === "plugins/source.js") {
126
133
  clay.source = mod.source || mod.default;