@particle-academy/fancy-cms-ui 0.9.0 → 0.10.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/CHANGELOG.md +31 -0
- package/README.md +46 -0
- package/dist/{chunk-BVMSHUQF.js → chunk-G7ULXTKI.js} +266 -33
- package/dist/chunk-G7ULXTKI.js.map +1 -0
- package/dist/editor.cjs +308 -75
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.js +2 -2
- package/dist/index.cjs +263 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{useEditor-B2kVZJA0.d.cts → useEditor-fIQEaKzZ.d.cts} +54 -2
- package/dist/{useEditor-BQSictH3.d.ts → useEditor-wGEqxpJT.d.ts} +54 -2
- package/package.json +2 -2
- package/dist/chunk-BVMSHUQF.js.map +0 -1
package/dist/editor.cjs
CHANGED
|
@@ -47,51 +47,7 @@ function setOwn(map, key, value) {
|
|
|
47
47
|
Object.defineProperty(map, key, { value, writable: true, enumerable: true, configurable: true });
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// src/document/
|
|
51
|
-
function normalizeDoc(input3) {
|
|
52
|
-
if (hasNodeMap(input3)) return input3;
|
|
53
|
-
const id = typeof input3 === "object" && input3 !== null && !Array.isArray(input3) && typeof input3.id === "string" ? input3.id : "malformed";
|
|
54
|
-
return emptyDoc(id);
|
|
55
|
-
}
|
|
56
|
-
function migrateDocVerbose(input3) {
|
|
57
|
-
const normalized = normalizeDoc(input3);
|
|
58
|
-
const legacy = normalized;
|
|
59
|
-
const sections = legacy.sections;
|
|
60
|
-
if (!Array.isArray(sections)) {
|
|
61
|
-
return { doc: normalized, migrated: false, danglingIds: [], unlistedRootIds: [] };
|
|
62
|
-
}
|
|
63
|
-
const danglingIds = [];
|
|
64
|
-
const ordered = [];
|
|
65
|
-
const seen = /* @__PURE__ */ new Set();
|
|
66
|
-
for (const id of sections) {
|
|
67
|
-
if (!own(normalized.nodes, id)) {
|
|
68
|
-
danglingIds.push(id);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (seen.has(id)) continue;
|
|
72
|
-
seen.add(id);
|
|
73
|
-
ordered.push(id);
|
|
74
|
-
}
|
|
75
|
-
const unlistedRootIds = Object.values(normalized.nodes).filter((n) => n.parent === null && !seen.has(n.id)).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0).map((n) => n.id);
|
|
76
|
-
const finalOrder = [...ordered, ...unlistedRootIds];
|
|
77
|
-
const nodes = { ...normalized.nodes };
|
|
78
|
-
let prev = null;
|
|
79
|
-
for (const id of finalOrder) {
|
|
80
|
-
const key = fancyDocCommons.fractionalKey(prev, null);
|
|
81
|
-
setOwn(nodes, id, { ...own(nodes, id), order: key });
|
|
82
|
-
prev = key;
|
|
83
|
-
}
|
|
84
|
-
const { sections: _dropped, ...rest } = legacy;
|
|
85
|
-
return {
|
|
86
|
-
doc: { ...rest, nodes },
|
|
87
|
-
migrated: true,
|
|
88
|
-
danglingIds,
|
|
89
|
-
unlistedRootIds
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function migrateDoc(input3) {
|
|
93
|
-
return migrateDocVerbose(input3).doc;
|
|
94
|
-
}
|
|
50
|
+
// src/document/reduce.ts
|
|
95
51
|
function childrenOf(doc, parent) {
|
|
96
52
|
return fancyDocCommons.childrenOf(doc, parent);
|
|
97
53
|
}
|
|
@@ -231,6 +187,50 @@ function reduce(doc, op, opts = {}) {
|
|
|
231
187
|
return invalid(`unknown op "${op.t}"`);
|
|
232
188
|
}
|
|
233
189
|
}
|
|
190
|
+
function normalizeDoc(input3) {
|
|
191
|
+
if (hasNodeMap(input3)) return input3;
|
|
192
|
+
const id = typeof input3 === "object" && input3 !== null && !Array.isArray(input3) && typeof input3.id === "string" ? input3.id : "malformed";
|
|
193
|
+
return emptyDoc(id);
|
|
194
|
+
}
|
|
195
|
+
function migrateDocVerbose(input3) {
|
|
196
|
+
const normalized = normalizeDoc(input3);
|
|
197
|
+
const legacy = normalized;
|
|
198
|
+
const sections = legacy.sections;
|
|
199
|
+
if (!Array.isArray(sections)) {
|
|
200
|
+
return { doc: normalized, migrated: false, danglingIds: [], unlistedRootIds: [] };
|
|
201
|
+
}
|
|
202
|
+
const danglingIds = [];
|
|
203
|
+
const ordered = [];
|
|
204
|
+
const seen = /* @__PURE__ */ new Set();
|
|
205
|
+
for (const id of sections) {
|
|
206
|
+
if (!own(normalized.nodes, id)) {
|
|
207
|
+
danglingIds.push(id);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (seen.has(id)) continue;
|
|
211
|
+
seen.add(id);
|
|
212
|
+
ordered.push(id);
|
|
213
|
+
}
|
|
214
|
+
const unlistedRootIds = Object.values(normalized.nodes).filter((n) => n.parent === null && !seen.has(n.id)).sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : 0).map((n) => n.id);
|
|
215
|
+
const finalOrder = [...ordered, ...unlistedRootIds];
|
|
216
|
+
const nodes = { ...normalized.nodes };
|
|
217
|
+
let prev = null;
|
|
218
|
+
for (const id of finalOrder) {
|
|
219
|
+
const key = fancyDocCommons.fractionalKey(prev, null);
|
|
220
|
+
setOwn(nodes, id, { ...own(nodes, id), order: key });
|
|
221
|
+
prev = key;
|
|
222
|
+
}
|
|
223
|
+
const { sections: _dropped, ...rest } = legacy;
|
|
224
|
+
return {
|
|
225
|
+
doc: { ...rest, nodes },
|
|
226
|
+
migrated: true,
|
|
227
|
+
danglingIds,
|
|
228
|
+
unlistedRootIds
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function migrateDoc(input3) {
|
|
232
|
+
return migrateDocVerbose(input3).doc;
|
|
233
|
+
}
|
|
234
234
|
function initEditor(doc) {
|
|
235
235
|
return { doc: migrateDoc(doc), past: [], future: [], selection: null };
|
|
236
236
|
}
|
|
@@ -900,16 +900,42 @@ var input = {
|
|
|
900
900
|
font: "inherit",
|
|
901
901
|
fontSize: 13
|
|
902
902
|
};
|
|
903
|
-
function Inspector({ doc, selection, apply }) {
|
|
903
|
+
function Inspector({ doc, selection, apply, drafts }) {
|
|
904
904
|
const uid = react.useId();
|
|
905
905
|
const fieldId = (key) => `${uid}-${key}`;
|
|
906
906
|
const node = selection ? doc.nodes[selection] : null;
|
|
907
907
|
if (!node) {
|
|
908
908
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, color: "var(--fcms-muted)", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
|
|
909
909
|
}
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
910
|
+
const nodeId = node.id;
|
|
911
|
+
const bind = (name, read, op, numeric = false) => {
|
|
912
|
+
const key = JSON.stringify([nodeId, name]);
|
|
913
|
+
const field2 = {
|
|
914
|
+
key,
|
|
915
|
+
label: `${nodeId} / ${name}`,
|
|
916
|
+
op,
|
|
917
|
+
read: (next) => {
|
|
918
|
+
const item = next.nodes[nodeId];
|
|
919
|
+
return item ? read(item) : void 0;
|
|
920
|
+
},
|
|
921
|
+
canonical: numeric ? (raw) => String(Number(raw) || 0) : (raw) => raw
|
|
922
|
+
};
|
|
923
|
+
const accepted = read(node);
|
|
924
|
+
return {
|
|
925
|
+
value: drafts ? drafts.value(key, accepted) : accepted,
|
|
926
|
+
onChange: (event) => {
|
|
927
|
+
if (drafts) drafts.change(field2, event.target.value);
|
|
928
|
+
else apply(op(event.target.value));
|
|
929
|
+
},
|
|
930
|
+
onFocus: () => drafts?.focus(key),
|
|
931
|
+
onBlur: () => drafts?.blur(key),
|
|
932
|
+
onCompositionStart: () => drafts?.composition(key, true),
|
|
933
|
+
onCompositionEnd: () => drafts?.composition(key, false)
|
|
934
|
+
};
|
|
935
|
+
};
|
|
936
|
+
const prop = (key) => bind(key, (item) => typeof item.props[key] === "string" ? item.props[key] : "", (raw) => ({ t: "set_node_props", id: nodeId, patch: { [key]: raw } }));
|
|
937
|
+
const style = (key) => bind(key, (item) => item.style.base[key] ?? "", (raw) => ({ t: "set_style", id: nodeId, breakpoint: "base", patch: { [key]: raw } }));
|
|
938
|
+
const length = (key) => bind(key, (item) => item.style.base[key]?.value?.toString() ?? "", (raw) => ({ t: "set_style", id: nodeId, breakpoint: "base", patch: { [key]: { value: Number(raw) || 0, unit: "px" } } }), true);
|
|
913
939
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", padding: 16, fontFamily: "system-ui, sans-serif", height: "100%", boxSizing: "border-box" }, children: [
|
|
914
940
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--fcms-fg)" }, children: node.type }),
|
|
915
941
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--fcms-muted)", marginBottom: 8 }, children: node.id }),
|
|
@@ -921,8 +947,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
921
947
|
id: fieldId("content"),
|
|
922
948
|
"data-cms-field": "content",
|
|
923
949
|
style: { ...input, minHeight: 64, resize: "vertical" },
|
|
924
|
-
|
|
925
|
-
onChange: (e) => setProp("content", e.target.value)
|
|
950
|
+
...prop("content")
|
|
926
951
|
}
|
|
927
952
|
)
|
|
928
953
|
] }) : null,
|
|
@@ -934,8 +959,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
934
959
|
id: fieldId("src"),
|
|
935
960
|
"data-cms-field": "src",
|
|
936
961
|
style: input,
|
|
937
|
-
|
|
938
|
-
onChange: (e) => setProp("src", e.target.value)
|
|
962
|
+
...prop("src")
|
|
939
963
|
}
|
|
940
964
|
)
|
|
941
965
|
] }) : null,
|
|
@@ -947,8 +971,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
947
971
|
"data-cms-field": "background",
|
|
948
972
|
style: input,
|
|
949
973
|
placeholder: "#ffffff | gradient | url(\u2026)",
|
|
950
|
-
|
|
951
|
-
onChange: (e) => setStyle({ background: e.target.value })
|
|
974
|
+
...style("background")
|
|
952
975
|
}
|
|
953
976
|
),
|
|
954
977
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("color"), children: "Text color" }),
|
|
@@ -959,8 +982,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
959
982
|
"data-cms-field": "color",
|
|
960
983
|
style: input,
|
|
961
984
|
placeholder: "#0f172a",
|
|
962
|
-
|
|
963
|
-
onChange: (e) => setStyle({ color: e.target.value })
|
|
985
|
+
...style("color")
|
|
964
986
|
}
|
|
965
987
|
),
|
|
966
988
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("fontSize"), children: "Font size (px)" }),
|
|
@@ -971,8 +993,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
971
993
|
"data-cms-field": "fontSize",
|
|
972
994
|
type: "number",
|
|
973
995
|
style: input,
|
|
974
|
-
|
|
975
|
-
onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
996
|
+
...length("fontSize")
|
|
976
997
|
}
|
|
977
998
|
),
|
|
978
999
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("padding"), children: "Padding (px)" }),
|
|
@@ -983,8 +1004,7 @@ function Inspector({ doc, selection, apply }) {
|
|
|
983
1004
|
"data-cms-field": "padding",
|
|
984
1005
|
type: "number",
|
|
985
1006
|
style: input,
|
|
986
|
-
|
|
987
|
-
onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
|
|
1007
|
+
...length("padding")
|
|
988
1008
|
}
|
|
989
1009
|
),
|
|
990
1010
|
/* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("opacity"), children: "Opacity" }),
|
|
@@ -998,12 +1018,171 @@ function Inspector({ doc, selection, apply }) {
|
|
|
998
1018
|
min: "0",
|
|
999
1019
|
max: "1",
|
|
1000
1020
|
style: input,
|
|
1001
|
-
|
|
1002
|
-
onChange: (e) => setStyle({ opacity: Number(e.target.value) })
|
|
1021
|
+
...bind("opacity", (item) => item.style.base.opacity?.toString() ?? "", (raw) => ({ t: "set_style", id: nodeId, breakpoint: "base", patch: { opacity: Number(raw) } }), true)
|
|
1003
1022
|
}
|
|
1004
1023
|
)
|
|
1005
1024
|
] });
|
|
1006
1025
|
}
|
|
1026
|
+
|
|
1027
|
+
// src/editor/InspectorDrafts.ts
|
|
1028
|
+
var InspectorDrafts = class {
|
|
1029
|
+
constructor(doc) {
|
|
1030
|
+
this.doc = doc;
|
|
1031
|
+
this.drafts = /* @__PURE__ */ new Map();
|
|
1032
|
+
this.composing = /* @__PURE__ */ new Set();
|
|
1033
|
+
this.focused = null;
|
|
1034
|
+
this.flight = null;
|
|
1035
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1036
|
+
this.version = 0;
|
|
1037
|
+
this.disabled = false;
|
|
1038
|
+
this.emit = () => false;
|
|
1039
|
+
this.subscribe = (listener) => {
|
|
1040
|
+
this.listeners.add(listener);
|
|
1041
|
+
return () => {
|
|
1042
|
+
this.listeners.delete(listener);
|
|
1043
|
+
};
|
|
1044
|
+
};
|
|
1045
|
+
this.snapshot = () => this.version;
|
|
1046
|
+
}
|
|
1047
|
+
get busy() {
|
|
1048
|
+
return this.flight !== null || [...this.drafts.values()].some((draft) => draft.dirty || draft.conflict !== null);
|
|
1049
|
+
}
|
|
1050
|
+
get conflicts() {
|
|
1051
|
+
return [...this.drafts.values()].filter((draft) => draft.conflict !== null).map((draft) => ({
|
|
1052
|
+
key: draft.field.key,
|
|
1053
|
+
label: draft.field.label,
|
|
1054
|
+
message: draft.conflict,
|
|
1055
|
+
missing: draft.field.read(this.doc) === void 0
|
|
1056
|
+
}));
|
|
1057
|
+
}
|
|
1058
|
+
value(key, accepted) {
|
|
1059
|
+
return this.drafts.get(key)?.raw ?? accepted;
|
|
1060
|
+
}
|
|
1061
|
+
configure(doc, disabled, emit) {
|
|
1062
|
+
const settled = this.disabled && !disabled;
|
|
1063
|
+
this.doc = doc;
|
|
1064
|
+
this.disabled = disabled;
|
|
1065
|
+
this.emit = emit;
|
|
1066
|
+
let changed = false;
|
|
1067
|
+
for (const [key, draft] of this.drafts) {
|
|
1068
|
+
const accepted = draft.field.read(doc);
|
|
1069
|
+
if (this.flight?.draft === draft && accepted === this.flight.expected) {
|
|
1070
|
+
this.clearFlight();
|
|
1071
|
+
draft.base = accepted;
|
|
1072
|
+
draft.dirty = draft.field.canonical(draft.raw) !== accepted;
|
|
1073
|
+
if (!draft.dirty && !draft.focused) this.drafts.delete(key);
|
|
1074
|
+
changed = true;
|
|
1075
|
+
} else if (accepted !== draft.base) {
|
|
1076
|
+
this.conflict(draft, accepted === void 0 ? "The edited element was removed. Your draft is retained." : "The server changed this field. Your draft is retained.");
|
|
1077
|
+
draft.base = accepted;
|
|
1078
|
+
changed = true;
|
|
1079
|
+
} else if (settled && this.flight?.draft === draft) {
|
|
1080
|
+
this.conflict(draft, "The save was not accepted. Your draft is retained.");
|
|
1081
|
+
changed = true;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (changed) this.notify();
|
|
1085
|
+
this.arm();
|
|
1086
|
+
}
|
|
1087
|
+
focus(key) {
|
|
1088
|
+
this.focused = key;
|
|
1089
|
+
const draft = this.drafts.get(key);
|
|
1090
|
+
if (draft) draft.focused = true;
|
|
1091
|
+
}
|
|
1092
|
+
change(field2, raw) {
|
|
1093
|
+
let draft = this.drafts.get(field2.key);
|
|
1094
|
+
if (!draft) {
|
|
1095
|
+
draft = { field: field2, raw, base: field2.read(this.doc), focused: this.focused === field2.key, dirty: true, conflict: null, readyAt: Date.now() + 250 };
|
|
1096
|
+
this.drafts.set(field2.key, draft);
|
|
1097
|
+
} else {
|
|
1098
|
+
draft.field = field2;
|
|
1099
|
+
draft.raw = raw;
|
|
1100
|
+
draft.readyAt = Date.now() + 250;
|
|
1101
|
+
}
|
|
1102
|
+
draft.dirty = field2.canonical(raw) !== draft.base;
|
|
1103
|
+
this.notify();
|
|
1104
|
+
this.arm();
|
|
1105
|
+
}
|
|
1106
|
+
blur(key) {
|
|
1107
|
+
if (this.focused === key) this.focused = null;
|
|
1108
|
+
const draft = this.drafts.get(key);
|
|
1109
|
+
if (draft) {
|
|
1110
|
+
draft.focused = false;
|
|
1111
|
+
draft.readyAt = Date.now();
|
|
1112
|
+
if (!draft.dirty && !draft.conflict && this.flight?.draft !== draft) this.drafts.delete(key);
|
|
1113
|
+
this.notify();
|
|
1114
|
+
this.pump();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
composition(key, active) {
|
|
1118
|
+
if (active) this.composing.add(key);
|
|
1119
|
+
else this.composing.delete(key);
|
|
1120
|
+
const draft = this.drafts.get(key);
|
|
1121
|
+
if (draft) draft.readyAt = Date.now() + 250;
|
|
1122
|
+
this.arm();
|
|
1123
|
+
}
|
|
1124
|
+
resolve(key, keep) {
|
|
1125
|
+
if (this.disabled) return;
|
|
1126
|
+
const draft = this.drafts.get(key);
|
|
1127
|
+
if (!draft) return;
|
|
1128
|
+
if (this.flight?.draft === draft) this.clearFlight();
|
|
1129
|
+
if (!keep) this.drafts.delete(key);
|
|
1130
|
+
else {
|
|
1131
|
+
const accepted = draft.field.read(this.doc);
|
|
1132
|
+
if (accepted === void 0) return;
|
|
1133
|
+
draft.base = accepted;
|
|
1134
|
+
draft.conflict = null;
|
|
1135
|
+
draft.dirty = draft.field.canonical(draft.raw) !== accepted;
|
|
1136
|
+
draft.readyAt = Date.now();
|
|
1137
|
+
if (!draft.dirty && !draft.focused) this.drafts.delete(key);
|
|
1138
|
+
}
|
|
1139
|
+
this.notify();
|
|
1140
|
+
this.pump();
|
|
1141
|
+
}
|
|
1142
|
+
dispose() {
|
|
1143
|
+
clearTimeout(this.timer);
|
|
1144
|
+
clearTimeout(this.ackTimer);
|
|
1145
|
+
}
|
|
1146
|
+
notify() {
|
|
1147
|
+
this.version++;
|
|
1148
|
+
for (const listener of this.listeners) listener();
|
|
1149
|
+
}
|
|
1150
|
+
clearFlight() {
|
|
1151
|
+
this.flight = null;
|
|
1152
|
+
clearTimeout(this.ackTimer);
|
|
1153
|
+
this.ackTimer = void 0;
|
|
1154
|
+
}
|
|
1155
|
+
conflict(draft, message) {
|
|
1156
|
+
draft.conflict = message;
|
|
1157
|
+
if (this.flight?.draft === draft) this.clearFlight();
|
|
1158
|
+
}
|
|
1159
|
+
arm() {
|
|
1160
|
+
clearTimeout(this.timer);
|
|
1161
|
+
this.timer = void 0;
|
|
1162
|
+
if (this.disabled || this.flight || this.conflicts.length > 0) return;
|
|
1163
|
+
const ready = [...this.drafts.values()].filter((draft) => draft.dirty && !this.composing.has(draft.field.key));
|
|
1164
|
+
if (ready.length) this.timer = setTimeout(() => this.pump(), Math.max(0, Math.min(...ready.map((draft) => draft.readyAt)) - Date.now()));
|
|
1165
|
+
}
|
|
1166
|
+
pump() {
|
|
1167
|
+
if (this.disabled || this.flight || this.conflicts.length > 0) return;
|
|
1168
|
+
const draft = [...this.drafts.values()].find((item) => item.dirty && item.readyAt <= Date.now() && !this.composing.has(item.field.key));
|
|
1169
|
+
if (!draft) {
|
|
1170
|
+
this.arm();
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
this.flight = { draft, expected: draft.field.canonical(draft.raw) };
|
|
1174
|
+
this.ackTimer = setTimeout(() => {
|
|
1175
|
+
this.conflict(draft, "The save has not been acknowledged. Your draft is retained.");
|
|
1176
|
+
this.notify();
|
|
1177
|
+
}, 1e4);
|
|
1178
|
+
try {
|
|
1179
|
+
if (!this.emit(draft.field.op(draft.raw))) this.conflict(draft, "This edit could not be submitted. Your draft is retained.");
|
|
1180
|
+
} catch {
|
|
1181
|
+
this.conflict(draft, "This edit could not be submitted. Your draft is retained.");
|
|
1182
|
+
}
|
|
1183
|
+
this.notify();
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1007
1186
|
var CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
1008
1187
|
"section",
|
|
1009
1188
|
"frame",
|
|
@@ -1077,18 +1256,59 @@ function buildInsertOp(doc, kind, selectedId, targetId) {
|
|
|
1077
1256
|
}
|
|
1078
1257
|
};
|
|
1079
1258
|
}
|
|
1080
|
-
function Editor({ defaultValue, onChange, registry, data }) {
|
|
1259
|
+
function Editor({ value, defaultValue, onChange, onOp, disabled = false, registry, data }) {
|
|
1081
1260
|
const initial = react.useMemo(() => defaultValue ?? emptyDoc("untitled"), [defaultValue]);
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1261
|
+
const local = useEditor(initial);
|
|
1262
|
+
const controlled = value !== void 0;
|
|
1263
|
+
const localDoc = useLatestRef(local.state.doc);
|
|
1264
|
+
const lastNotified = react.useRef(local.state.doc);
|
|
1265
|
+
const onOpRef = useLatestRef(onOp);
|
|
1266
|
+
const [drafts] = react.useState(() => new InspectorDrafts(value ?? initial));
|
|
1267
|
+
react.useSyncExternalStore(drafts.subscribe, drafts.snapshot, drafts.snapshot);
|
|
1084
1268
|
const onChangeRef = useLatestRef(onChange);
|
|
1085
1269
|
react.useEffect(() => {
|
|
1086
|
-
if (
|
|
1087
|
-
|
|
1088
|
-
|
|
1270
|
+
if (controlled || lastNotified.current === local.state.doc) return;
|
|
1271
|
+
lastNotified.current = local.state.doc;
|
|
1272
|
+
onChangeRef.current?.(local.state.doc);
|
|
1273
|
+
}, [controlled, local.state.doc, onChangeRef]);
|
|
1274
|
+
const apply = (op) => {
|
|
1275
|
+
if (disabled) return false;
|
|
1276
|
+
const source = value ?? localDoc.current;
|
|
1277
|
+
const next = reduce(source, op);
|
|
1278
|
+
if (next === source) return false;
|
|
1279
|
+
if (!controlled) {
|
|
1280
|
+
localDoc.current = next;
|
|
1281
|
+
local.apply(op);
|
|
1089
1282
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1283
|
+
onOpRef.current?.(op);
|
|
1284
|
+
if (controlled) onChangeRef.current?.(next);
|
|
1285
|
+
return true;
|
|
1286
|
+
};
|
|
1287
|
+
react.useEffect(() => {
|
|
1288
|
+
if (controlled) drafts.configure(value, disabled, apply);
|
|
1289
|
+
});
|
|
1290
|
+
react.useEffect(() => () => drafts.dispose(), [drafts]);
|
|
1291
|
+
const structuralDisabled = disabled || controlled && drafts.busy;
|
|
1292
|
+
const ed = {
|
|
1293
|
+
...local,
|
|
1294
|
+
state: { ...local.state, doc: value ?? local.state.doc },
|
|
1295
|
+
apply: (op) => {
|
|
1296
|
+
if (!structuralDisabled) apply(op);
|
|
1297
|
+
},
|
|
1298
|
+
// Snapshot history belongs to the local document. Replaying it into a
|
|
1299
|
+
// server-owned value could overwrite another writer's accepted work.
|
|
1300
|
+
undo: () => {
|
|
1301
|
+
if (!controlled && !disabled) local.undo();
|
|
1302
|
+
},
|
|
1303
|
+
redo: () => {
|
|
1304
|
+
if (!controlled && !disabled) local.redo();
|
|
1305
|
+
},
|
|
1306
|
+
replace: (next) => {
|
|
1307
|
+
if (!controlled && !disabled) local.replace(next);
|
|
1308
|
+
},
|
|
1309
|
+
canUndo: !controlled && !disabled && local.canUndo,
|
|
1310
|
+
canRedo: !controlled && !disabled && local.canRedo
|
|
1311
|
+
};
|
|
1092
1312
|
const shell = {
|
|
1093
1313
|
display: "grid",
|
|
1094
1314
|
gridTemplateColumns: "220px 1fr 300px",
|
|
@@ -1100,9 +1320,9 @@ function Editor({ defaultValue, onChange, registry, data }) {
|
|
|
1100
1320
|
borderRadius: 12,
|
|
1101
1321
|
overflow: "hidden"
|
|
1102
1322
|
};
|
|
1103
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fancy-cms-editor", style: shell, children: [
|
|
1323
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fancy-cms-editor", style: shell, "aria-busy": structuralDisabled || void 0, children: [
|
|
1104
1324
|
/* @__PURE__ */ jsxRuntime.jsx("style", { children: CHROME_CSS }),
|
|
1105
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid var(--fcms-border)", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
1325
|
+
/* @__PURE__ */ jsxRuntime.jsx("fieldset", { disabled: structuralDisabled, "aria-label": "Page structure controls", style: { display: "contents", border: 0, padding: 0, margin: 0 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRight: "1px solid var(--fcms-border)", display: "flex", flexDirection: "column", minHeight: 0 }, children: [
|
|
1106
1326
|
/* @__PURE__ */ jsxRuntime.jsx(Toolbar, { ed }),
|
|
1107
1327
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1108
1328
|
LayersPanel,
|
|
@@ -1113,9 +1333,22 @@ function Editor({ defaultValue, onChange, registry, data }) {
|
|
|
1113
1333
|
onMove: (id, parent, order) => ed.apply({ t: "move_node", id, parent, order })
|
|
1114
1334
|
}
|
|
1115
1335
|
)
|
|
1116
|
-
] }),
|
|
1336
|
+
] }) }),
|
|
1117
1337
|
/* @__PURE__ */ jsxRuntime.jsx(Canvas, { doc: ed.state.doc, selection: ed.state.selection, onSelect: ed.select, apply: ed.apply, registry, data }),
|
|
1118
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1338
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderLeft: "1px solid var(--fcms-border)", minHeight: 0, overflow: "auto" }, children: [
|
|
1339
|
+
controlled && drafts.conflicts.map((conflict) => /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", "data-cms-draft-conflict": conflict.key, style: { padding: 12, fontFamily: "system-ui,sans-serif", fontSize: 13 }, children: [
|
|
1340
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
1341
|
+
conflict.label,
|
|
1342
|
+
": ",
|
|
1343
|
+
conflict.message
|
|
1344
|
+
] }),
|
|
1345
|
+
/* @__PURE__ */ jsxRuntime.jsx("pre", { "data-cms-retained-draft": "", style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: drafts.value(conflict.key, "") }),
|
|
1346
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled, onClick: () => drafts.resolve(conflict.key, false), children: "Use server value" }),
|
|
1347
|
+
" ",
|
|
1348
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: disabled || conflict.missing, onClick: () => drafts.resolve(conflict.key, true), children: "Keep draft" })
|
|
1349
|
+
] }, conflict.key)),
|
|
1350
|
+
/* @__PURE__ */ jsxRuntime.jsx("fieldset", { disabled: !controlled && disabled, "aria-label": "Page property controls", style: { border: 0, padding: 0, margin: 0, minWidth: 0, height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(Inspector, { doc: ed.state.doc, selection: ed.state.selection, apply: ed.apply, drafts: controlled ? drafts : void 0 }) })
|
|
1351
|
+
] })
|
|
1119
1352
|
] });
|
|
1120
1353
|
}
|
|
1121
1354
|
function Toolbar({ ed }) {
|