@nextbridgehq/payload-block-builder 0.1.7 → 0.1.9
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/README.md +250 -39
- package/dist/bin/init.js +79 -25
- package/dist/client.cjs +473 -302
- package/dist/client.d.cts +5 -1
- package/dist/client.d.ts +5 -1
- package/dist/client.js +308 -124
- package/dist/index.cjs +45 -15
- package/dist/index.js +45 -15
- package/package.json +2 -2
- package/src/components/BlockDataField/BlockDataField.css +170 -0
package/dist/client.js
CHANGED
|
@@ -39,6 +39,133 @@ function MediaPicker({ label, required, value, onChange }) {
|
|
|
39
39
|
"x"
|
|
40
40
|
))) : /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from Media Library")), /* @__PURE__ */ React.createElement(ListDrawer, { onSelect: handleSelect }));
|
|
41
41
|
}
|
|
42
|
+
function toRelDoc(v) {
|
|
43
|
+
if (!v) return null;
|
|
44
|
+
if (typeof v === "object") {
|
|
45
|
+
const o = v;
|
|
46
|
+
if (!o.id) return null;
|
|
47
|
+
return { id: o.id, title: o.title ? String(o.title) : null };
|
|
48
|
+
}
|
|
49
|
+
if (typeof v === "string" || typeof v === "number") return { id: v, title: null };
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function RelationshipPicker({ label, required, collection, hasMany = false, value, onChange }) {
|
|
53
|
+
const changeRef = useRef(onChange);
|
|
54
|
+
const closeRef = useRef(() => {
|
|
55
|
+
});
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
changeRef.current = onChange;
|
|
58
|
+
});
|
|
59
|
+
const items = hasMany ? Array.isArray(value) ? value.map(toRelDoc).filter(Boolean) : [] : (() => {
|
|
60
|
+
const d = toRelDoc(value);
|
|
61
|
+
return d ? [d] : [];
|
|
62
|
+
})();
|
|
63
|
+
const itemsRef = useRef(items);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
itemsRef.current = items;
|
|
66
|
+
});
|
|
67
|
+
const handleSelect = useCallback(
|
|
68
|
+
({ docID, doc }) => {
|
|
69
|
+
const title = String(doc?.title ?? doc?.name ?? doc?.slug ?? "") || null;
|
|
70
|
+
const entry = { id: docID, title };
|
|
71
|
+
if (hasMany) {
|
|
72
|
+
const alreadyExists = itemsRef.current.some((i) => String(i.id) === String(docID));
|
|
73
|
+
if (!alreadyExists) changeRef.current([...itemsRef.current, entry]);
|
|
74
|
+
} else {
|
|
75
|
+
changeRef.current(entry);
|
|
76
|
+
closeRef.current();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
[hasMany]
|
|
80
|
+
);
|
|
81
|
+
const [ListDrawer, ListDrawerToggler, { closeDrawer, openDrawer }] = useListDrawer({
|
|
82
|
+
collectionSlugs: [collection]
|
|
83
|
+
});
|
|
84
|
+
closeRef.current = closeDrawer;
|
|
85
|
+
const [fetchedTitles, setFetchedTitles] = useState({});
|
|
86
|
+
const fetchingRef = useRef(/* @__PURE__ */ new Set());
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const missing = items.filter(
|
|
89
|
+
(i) => !i.title && !fetchedTitles[String(i.id)] && !fetchingRef.current.has(String(i.id))
|
|
90
|
+
);
|
|
91
|
+
if (missing.length === 0) return;
|
|
92
|
+
missing.forEach((item) => {
|
|
93
|
+
const idStr = String(item.id);
|
|
94
|
+
fetchingRef.current.add(idStr);
|
|
95
|
+
fetch(`/api/${collection}/${idStr}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
|
|
96
|
+
if (doc) {
|
|
97
|
+
const t = doc.title ?? doc.name ?? doc.slug ?? null;
|
|
98
|
+
if (t) setFetchedTitles((prev) => ({ ...prev, [idStr]: String(t) }));
|
|
99
|
+
}
|
|
100
|
+
}).catch(() => {
|
|
101
|
+
fetchingRef.current.delete(idStr);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}, [items, collection]);
|
|
105
|
+
function getTitle(item) {
|
|
106
|
+
return item.title ?? fetchedTitles[String(item.id)] ?? `ID: ${String(item.id)}`;
|
|
107
|
+
}
|
|
108
|
+
function clearOne(e, id) {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
if (hasMany) onChange(items.filter((i) => String(i.id) !== String(id)));
|
|
111
|
+
else onChange(null);
|
|
112
|
+
}
|
|
113
|
+
return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ React.createElement("div", { className: `bdf-rel${hasMany ? " bdf-rel--multi" : ""}` }, /* @__PURE__ */ React.createElement(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
role: "button",
|
|
117
|
+
tabIndex: 0,
|
|
118
|
+
className: "bdf-rel__control",
|
|
119
|
+
onClick: openDrawer,
|
|
120
|
+
onKeyDown: (e) => {
|
|
121
|
+
if (e.key === "Enter" || e.key === " ") openDrawer();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
/* @__PURE__ */ React.createElement("div", { className: "bdf-rel__values" }, items.length === 0 && /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__placeholder" }, "Select a value..."), !hasMany && items.length > 0 && /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__single" }, getTitle(items[0])), hasMany && items.map((item) => /* @__PURE__ */ React.createElement("span", { key: String(item.id), className: "bdf-rel__chip" }, /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__chip-label" }, getTitle(item)), /* @__PURE__ */ React.createElement(
|
|
125
|
+
"button",
|
|
126
|
+
{
|
|
127
|
+
type: "button",
|
|
128
|
+
className: "bdf-rel__chip-remove",
|
|
129
|
+
onClick: (e) => clearOne(e, item.id),
|
|
130
|
+
"aria-label": `Remove ${getTitle(item)}`
|
|
131
|
+
},
|
|
132
|
+
/* @__PURE__ */ React.createElement("svg", { height: "12", width: "12", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z" }))
|
|
133
|
+
)))),
|
|
134
|
+
/* @__PURE__ */ React.createElement("div", { className: "bdf-rel__indicators" }, !hasMany && items.length > 0 && /* @__PURE__ */ React.createElement(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
type: "button",
|
|
138
|
+
className: "bdf-rel__clear",
|
|
139
|
+
onClick: (e) => clearOne(e, items[0].id),
|
|
140
|
+
"aria-label": "Clear"
|
|
141
|
+
},
|
|
142
|
+
/* @__PURE__ */ React.createElement("svg", { height: "16", width: "16", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z" }))
|
|
143
|
+
), /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__sep" }), /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__chevron" }, /* @__PURE__ */ React.createElement("svg", { height: "16", width: "16", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z" }))))
|
|
144
|
+
), hasMany && /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-rel__add", onClick: (e) => e.stopPropagation() }, "+")), /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__hint" }, collection, hasMany ? " \xB7 multiple" : ""), /* @__PURE__ */ React.createElement(ListDrawer, { onSelect: handleSelect }));
|
|
145
|
+
}
|
|
146
|
+
function JsonField({ label, required, value, onChange }) {
|
|
147
|
+
const [text, setText] = useState(
|
|
148
|
+
() => value !== void 0 ? JSON.stringify(value, null, 2) : ""
|
|
149
|
+
);
|
|
150
|
+
const [hasError, setHasError] = useState(false);
|
|
151
|
+
return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ React.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ React.createElement(
|
|
152
|
+
"textarea",
|
|
153
|
+
{
|
|
154
|
+
className: "bdf-input bdf-textarea bdf-mono",
|
|
155
|
+
value: text,
|
|
156
|
+
rows: 4,
|
|
157
|
+
onChange: (e) => {
|
|
158
|
+
setText(e.target.value);
|
|
159
|
+
try {
|
|
160
|
+
onChange(JSON.parse(e.target.value));
|
|
161
|
+
setHasError(false);
|
|
162
|
+
} catch {
|
|
163
|
+
setHasError(true);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
), hasError && /* @__PURE__ */ React.createElement("div", { className: "bdf-error", style: { marginTop: 4 } }, "Invalid JSON \u2014 changes not saved until fixed."));
|
|
168
|
+
}
|
|
42
169
|
function SchemaForm({ schema, value, onChange }) {
|
|
43
170
|
const set = useCallback(
|
|
44
171
|
(key, val) => onChange({ ...value, [key]: val }),
|
|
@@ -64,7 +191,7 @@ function FieldInput({ field, value, onChange }) {
|
|
|
64
191
|
"input",
|
|
65
192
|
{
|
|
66
193
|
className: "bdf-input",
|
|
67
|
-
type: field.type === "email" ? "email" : "text",
|
|
194
|
+
type: field.type === "email" ? "email" : field.type === "url" ? "url" : "text",
|
|
68
195
|
value: value ?? "",
|
|
69
196
|
onChange: (e) => onChange(e.target.value)
|
|
70
197
|
}
|
|
@@ -117,7 +244,7 @@ function FieldInput({ field, value, onChange }) {
|
|
|
117
244
|
className: "bdf-input",
|
|
118
245
|
type: "number",
|
|
119
246
|
value: numVal,
|
|
120
|
-
onChange: (e) => onChange(e.target.value === "" ?
|
|
247
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.valueAsNumber)
|
|
121
248
|
}
|
|
122
249
|
));
|
|
123
250
|
}
|
|
@@ -176,32 +303,25 @@ function FieldInput({ field, value, onChange }) {
|
|
|
176
303
|
onChange
|
|
177
304
|
}
|
|
178
305
|
);
|
|
179
|
-
case "relationship":
|
|
180
|
-
|
|
181
|
-
|
|
306
|
+
case "relationship": {
|
|
307
|
+
const relField = field;
|
|
308
|
+
if (!relField.collection) {
|
|
309
|
+
return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label), /* @__PURE__ */ React.createElement("div", { className: "bdf-error" }, "Relationship field ", /* @__PURE__ */ React.createElement("strong", null, field.name), " has no ", /* @__PURE__ */ React.createElement("code", null, "collection"), " defined in its schema."));
|
|
310
|
+
}
|
|
311
|
+
return /* @__PURE__ */ React.createElement(
|
|
312
|
+
RelationshipPicker,
|
|
182
313
|
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
314
|
+
label,
|
|
315
|
+
required: field.required,
|
|
316
|
+
collection: relField.collection,
|
|
317
|
+
hasMany: relField.hasMany ?? false,
|
|
318
|
+
value,
|
|
319
|
+
onChange
|
|
188
320
|
}
|
|
189
|
-
)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
190
323
|
case "json":
|
|
191
|
-
return /* @__PURE__ */ React.createElement(
|
|
192
|
-
"textarea",
|
|
193
|
-
{
|
|
194
|
-
className: "bdf-input bdf-textarea bdf-mono",
|
|
195
|
-
value: value !== void 0 ? JSON.stringify(value, null, 2) : "",
|
|
196
|
-
rows: 4,
|
|
197
|
-
onChange: (e) => {
|
|
198
|
-
try {
|
|
199
|
-
onChange(JSON.parse(e.target.value));
|
|
200
|
-
} catch {
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
));
|
|
324
|
+
return /* @__PURE__ */ React.createElement(JsonField, { label, required: field.required, value, onChange });
|
|
205
325
|
case "array": {
|
|
206
326
|
const rows = Array.isArray(value) ? value : [];
|
|
207
327
|
const subFields = field.fields ?? [];
|
|
@@ -297,9 +417,37 @@ function BlockDataField({ path }) {
|
|
|
297
417
|
)));
|
|
298
418
|
}
|
|
299
419
|
|
|
420
|
+
// src/components/BlockVersionSync/index.tsx
|
|
421
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
422
|
+
import { useField as useField2, useFormFields as useFormFields2 } from "@payloadcms/ui";
|
|
423
|
+
function BlockVersionSync({ path }) {
|
|
424
|
+
const blockVersionPath = path.replace(/\.blockDefinition$/, ".blockVersion");
|
|
425
|
+
const { setValue: setVersion } = useField2({ path: blockVersionPath });
|
|
426
|
+
const blockDefValue = useFormFields2(([fields]) => fields[path]?.value);
|
|
427
|
+
const prevDefIdRef = useRef2(null);
|
|
428
|
+
useEffect2(() => {
|
|
429
|
+
const defId = blockDefValue && typeof blockDefValue === "object" ? blockDefValue.id : typeof blockDefValue === "string" || typeof blockDefValue === "number" ? blockDefValue : null;
|
|
430
|
+
if (defId === prevDefIdRef.current) return;
|
|
431
|
+
prevDefIdRef.current = defId;
|
|
432
|
+
if (!defId) {
|
|
433
|
+
setVersion(null);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
fetch(`/api/block-definitions/${String(defId)}?depth=1`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
|
|
437
|
+
if (!doc) return;
|
|
438
|
+
const currentVersion = doc.currentVersion;
|
|
439
|
+
if (!currentVersion) return;
|
|
440
|
+
const versionId = typeof currentVersion === "object" ? currentVersion.id : currentVersion;
|
|
441
|
+
if (versionId) setVersion(versionId);
|
|
442
|
+
}).catch(() => {
|
|
443
|
+
});
|
|
444
|
+
}, [blockDefValue, setVersion]);
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
300
448
|
// src/components/SchemaBuilderField/index.tsx
|
|
301
|
-
import React5, { useCallback as useCallback3, useEffect as
|
|
302
|
-
import { useField as
|
|
449
|
+
import React5, { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
450
|
+
import { useField as useField3 } from "@payloadcms/ui";
|
|
303
451
|
|
|
304
452
|
// src/components/SchemaBuilderField/FieldRow.tsx
|
|
305
453
|
import React4, { useState as useState2 } from "react";
|
|
@@ -873,22 +1021,22 @@ function parseSchema(raw) {
|
|
|
873
1021
|
return [];
|
|
874
1022
|
}
|
|
875
1023
|
function SchemaBuilderField({ path, readOnly }) {
|
|
876
|
-
const { value, setValue } =
|
|
877
|
-
const setValueRef =
|
|
878
|
-
|
|
1024
|
+
const { value, setValue } = useField3({ path });
|
|
1025
|
+
const setValueRef = useRef3(setValue);
|
|
1026
|
+
useEffect3(() => {
|
|
879
1027
|
setValueRef.current = setValue;
|
|
880
1028
|
});
|
|
881
1029
|
const [fields, setFields] = useState3(() => parseSchema(value));
|
|
882
1030
|
const hasExistingValue = value !== void 0 && value !== null;
|
|
883
1031
|
const [hydrated, setHydrated] = useState3(!hasExistingValue);
|
|
884
|
-
|
|
1032
|
+
useEffect3(() => {
|
|
885
1033
|
if (hydrated) return;
|
|
886
1034
|
if (value !== void 0 && value !== null) {
|
|
887
1035
|
setFields(parseSchema(value));
|
|
888
1036
|
setHydrated(true);
|
|
889
1037
|
}
|
|
890
1038
|
}, [value, hydrated]);
|
|
891
|
-
|
|
1039
|
+
useEffect3(() => {
|
|
892
1040
|
if (!hydrated) return;
|
|
893
1041
|
setValueRef.current({ fields });
|
|
894
1042
|
}, [fields, hydrated]);
|
|
@@ -945,10 +1093,10 @@ function SchemaBuilderField({ path, readOnly }) {
|
|
|
945
1093
|
// src/components/EditInBuilderButton/index.tsx
|
|
946
1094
|
import React6 from "react";
|
|
947
1095
|
import { useDocumentInfo } from "@payloadcms/ui";
|
|
948
|
-
import { useField as
|
|
1096
|
+
import { useField as useField4 } from "@payloadcms/ui";
|
|
949
1097
|
function EditInBuilderButton() {
|
|
950
1098
|
const { id } = useDocumentInfo();
|
|
951
|
-
const { value: slug } =
|
|
1099
|
+
const { value: slug } = useField4({ path: "slug" });
|
|
952
1100
|
if (!id || !slug) return null;
|
|
953
1101
|
return /* @__PURE__ */ React6.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ React6.createElement(
|
|
954
1102
|
"a",
|
|
@@ -976,7 +1124,7 @@ function EditInBuilderButton() {
|
|
|
976
1124
|
}
|
|
977
1125
|
|
|
978
1126
|
// src/block-builder/components/canvas/BuilderShell.tsx
|
|
979
|
-
import React16, { useCallback as useCallback5, useEffect as
|
|
1127
|
+
import React16, { useCallback as useCallback5, useEffect as useEffect6, useState as useState8 } from "react";
|
|
980
1128
|
|
|
981
1129
|
// src/block-builder/store/builder.store.ts
|
|
982
1130
|
import { create } from "zustand";
|
|
@@ -1133,7 +1281,8 @@ var useBuilderStore = create()(
|
|
|
1133
1281
|
);
|
|
1134
1282
|
|
|
1135
1283
|
// src/block-builder/components/canvas/TopBar.tsx
|
|
1136
|
-
import React7, { useEffect as
|
|
1284
|
+
import React7, { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1285
|
+
import { Blocks, ChevronDown, X } from "lucide-react";
|
|
1137
1286
|
|
|
1138
1287
|
// src/block-builder/lib/mapToSaveRequest.ts
|
|
1139
1288
|
var TYPE_MAP = {
|
|
@@ -1287,7 +1436,7 @@ function generateIndexFile(blocks) {
|
|
|
1287
1436
|
}
|
|
1288
1437
|
|
|
1289
1438
|
// src/block-builder/components/canvas/TopBar.tsx
|
|
1290
|
-
function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish }) {
|
|
1439
|
+
function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish, notification, onSetNotification }) {
|
|
1291
1440
|
const blocks = useBuilderStore((s) => s.blocks);
|
|
1292
1441
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
1293
1442
|
const activeBlock = blocks.find((b) => b.id === activeBlockId);
|
|
@@ -1295,21 +1444,21 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1295
1444
|
const isDirty = useBuilderStore((s) => s.isDirty);
|
|
1296
1445
|
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
1297
1446
|
const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
|
|
1298
|
-
const
|
|
1447
|
+
const setNotification = onSetNotification;
|
|
1299
1448
|
const [versionDropdownOpen, setVersionDropdownOpen] = useState4(false);
|
|
1300
1449
|
const [blockPickerOpen, setBlockPickerOpen] = useState4(false);
|
|
1301
|
-
const dropdownRef =
|
|
1302
|
-
const blockPickerRef =
|
|
1450
|
+
const dropdownRef = useRef4(null);
|
|
1451
|
+
const blockPickerRef = useRef4(null);
|
|
1303
1452
|
const selectedVersion = versions.find((v) => v.id === selectedVersionId);
|
|
1304
1453
|
const currentVersion = versions.find((v) => v.isCurrent);
|
|
1305
1454
|
const activeBlockDef = blockDefs.find((b) => b.slug === activeSlug);
|
|
1306
|
-
|
|
1455
|
+
useEffect4(() => {
|
|
1307
1456
|
if (notification?.status === "success") {
|
|
1308
1457
|
const t = setTimeout(() => setNotification(null), 3e3);
|
|
1309
1458
|
return () => clearTimeout(t);
|
|
1310
1459
|
}
|
|
1311
1460
|
}, [notification]);
|
|
1312
|
-
|
|
1461
|
+
useEffect4(() => {
|
|
1313
1462
|
function handleClick(e) {
|
|
1314
1463
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
1315
1464
|
setVersionDropdownOpen(false);
|
|
@@ -1322,7 +1471,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1322
1471
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
1323
1472
|
}, []);
|
|
1324
1473
|
async function handlePublish() {
|
|
1325
|
-
if (!activeBlock || isReadOnly) return;
|
|
1474
|
+
if (!activeBlock || isReadOnly) return false;
|
|
1326
1475
|
setNotification({ status: "publishing" });
|
|
1327
1476
|
try {
|
|
1328
1477
|
const req = mapToSaveRequest(activeBlock);
|
|
@@ -1339,13 +1488,15 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1339
1488
|
status: "success",
|
|
1340
1489
|
msg: `v${json.versionNumber ?? "?"} published successfully!`
|
|
1341
1490
|
});
|
|
1342
|
-
|
|
1491
|
+
onAfterPublish();
|
|
1492
|
+
return true;
|
|
1343
1493
|
} else {
|
|
1344
1494
|
setNotification({
|
|
1345
1495
|
status: "error",
|
|
1346
1496
|
title: "Failed to publish block",
|
|
1347
1497
|
errors: json.errors ?? ["An unknown error occurred."]
|
|
1348
1498
|
});
|
|
1499
|
+
return false;
|
|
1349
1500
|
}
|
|
1350
1501
|
} catch (err) {
|
|
1351
1502
|
setNotification({
|
|
@@ -1353,6 +1504,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1353
1504
|
title: "Network error",
|
|
1354
1505
|
errors: [err instanceof Error ? err.message : "Could not reach the server."]
|
|
1355
1506
|
});
|
|
1507
|
+
return false;
|
|
1356
1508
|
}
|
|
1357
1509
|
}
|
|
1358
1510
|
function handleExport() {
|
|
@@ -1380,9 +1532,9 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1380
1532
|
className: "bb-block-picker__trigger",
|
|
1381
1533
|
onClick: () => setBlockPickerOpen((o) => !o)
|
|
1382
1534
|
},
|
|
1383
|
-
/* @__PURE__ */ React7.createElement(
|
|
1535
|
+
/* @__PURE__ */ React7.createElement(Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
|
|
1384
1536
|
/* @__PURE__ */ React7.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
|
|
1385
|
-
/* @__PURE__ */ React7.createElement(
|
|
1537
|
+
/* @__PURE__ */ React7.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
|
|
1386
1538
|
), blockPickerOpen && /* @__PURE__ */ React7.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ React7.createElement(
|
|
1387
1539
|
"button",
|
|
1388
1540
|
{
|
|
@@ -1406,7 +1558,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1406
1558
|
/* @__PURE__ */ React7.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
|
|
1407
1559
|
/* @__PURE__ */ React7.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
|
|
1408
1560
|
selectedVersion?.isCurrent && /* @__PURE__ */ React7.createElement("span", { className: "bb-version-selector__badge" }, "current"),
|
|
1409
|
-
/* @__PURE__ */ React7.createElement(
|
|
1561
|
+
/* @__PURE__ */ React7.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
|
|
1410
1562
|
), versionDropdownOpen && /* @__PURE__ */ React7.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ React7.createElement(
|
|
1411
1563
|
"button",
|
|
1412
1564
|
{
|
|
@@ -1447,9 +1599,8 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1447
1599
|
{
|
|
1448
1600
|
type: "button",
|
|
1449
1601
|
onClick: async () => {
|
|
1450
|
-
await handlePublish();
|
|
1451
|
-
|
|
1452
|
-
onRestoreVersion();
|
|
1602
|
+
const success = await handlePublish();
|
|
1603
|
+
if (success) onRestoreVersion();
|
|
1453
1604
|
},
|
|
1454
1605
|
disabled: notification?.status === "publishing" || !activeBlock,
|
|
1455
1606
|
className: "bb-btn bb-btn--warning"
|
|
@@ -1464,19 +1615,38 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1464
1615
|
className: "bb-btn bb-btn--primary"
|
|
1465
1616
|
},
|
|
1466
1617
|
notification?.status === "publishing" ? "Publishing..." : "Publish to Payload"
|
|
1467
|
-
))), notification?.status === "publishing" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ React7.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) },
|
|
1618
|
+
))), notification?.status === "publishing" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ React7.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ React7.createElement(X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ React7.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ React7.createElement("li", { key: i }, e)))), /* @__PURE__ */ React7.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ React7.createElement(X, { size: 12, strokeWidth: 2 })))));
|
|
1468
1619
|
}
|
|
1469
1620
|
|
|
1470
1621
|
// src/block-builder/components/canvas/BlockList.tsx
|
|
1471
1622
|
import React8 from "react";
|
|
1472
|
-
|
|
1623
|
+
import { Copy, Trash2, Plus } from "lucide-react";
|
|
1624
|
+
function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
|
|
1473
1625
|
const blocks = useBuilderStore((s) => s.blocks);
|
|
1474
1626
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
1475
1627
|
const addBlock = useBuilderStore((s) => s.addBlock);
|
|
1476
1628
|
const removeBlock = useBuilderStore((s) => s.removeBlock);
|
|
1477
1629
|
const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
|
|
1478
1630
|
const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
|
|
1479
|
-
|
|
1631
|
+
const useApiNav = blockDefs.length > 0;
|
|
1632
|
+
const loadedBlock = useApiNav ? blocks.find((b) => b.slug === activeSlug) : null;
|
|
1633
|
+
const localOnlyBlocks = blocks.filter(
|
|
1634
|
+
(b) => !blockDefs.some((d) => d.slug === b.slug)
|
|
1635
|
+
);
|
|
1636
|
+
return /* @__PURE__ */ React8.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ React8.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ React8.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ React8.createElement(Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ React8.createElement("div", { className: "bb-sidebar__body" }, useApiNav && blockDefs.map((def) => {
|
|
1637
|
+
const isActive = def.slug === activeSlug;
|
|
1638
|
+
const fieldCount = isActive && loadedBlock ? loadedBlock.fields.length : null;
|
|
1639
|
+
return /* @__PURE__ */ React8.createElement(
|
|
1640
|
+
"div",
|
|
1641
|
+
{
|
|
1642
|
+
key: def.id,
|
|
1643
|
+
onClick: () => onBlockSelect?.(def.slug),
|
|
1644
|
+
className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
|
|
1645
|
+
},
|
|
1646
|
+
/* @__PURE__ */ React8.createElement("div", { className: "bb-block-item__slug" }, def.slug),
|
|
1647
|
+
/* @__PURE__ */ React8.createElement("div", { className: "bb-block-item__meta" }, fieldCount !== null ? `${fieldCount} field${fieldCount !== 1 ? "s" : ""}` : def.name)
|
|
1648
|
+
);
|
|
1649
|
+
}), localOnlyBlocks.map((block) => {
|
|
1480
1650
|
const isActive = block.id === activeBlockId;
|
|
1481
1651
|
return /* @__PURE__ */ React8.createElement(
|
|
1482
1652
|
"div",
|
|
@@ -1501,7 +1671,7 @@ function BlockList() {
|
|
|
1501
1671
|
className: "bb-block-action",
|
|
1502
1672
|
title: "Duplicate"
|
|
1503
1673
|
},
|
|
1504
|
-
|
|
1674
|
+
/* @__PURE__ */ React8.createElement(Copy, { size: 12, strokeWidth: 1.75 })
|
|
1505
1675
|
),
|
|
1506
1676
|
/* @__PURE__ */ React8.createElement(
|
|
1507
1677
|
"button",
|
|
@@ -1511,11 +1681,11 @@ function BlockList() {
|
|
|
1511
1681
|
className: "bb-block-action bb-block-action--danger",
|
|
1512
1682
|
title: "Delete"
|
|
1513
1683
|
},
|
|
1514
|
-
|
|
1684
|
+
/* @__PURE__ */ React8.createElement(Trash2, { size: 12, strokeWidth: 1.75 })
|
|
1515
1685
|
)
|
|
1516
1686
|
)
|
|
1517
1687
|
);
|
|
1518
|
-
})));
|
|
1688
|
+
}), !useApiNav && blocks.length === 0 && /* @__PURE__ */ React8.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ React8.createElement("br", null), "Click + to create one.")));
|
|
1519
1689
|
}
|
|
1520
1690
|
|
|
1521
1691
|
// src/block-builder/components/canvas/BuilderCanvas.tsx
|
|
@@ -1539,24 +1709,32 @@ import { restrictToVerticalAxis, restrictToParentElement } from "@dnd-kit/modifi
|
|
|
1539
1709
|
import React9 from "react";
|
|
1540
1710
|
import { useSortable } from "@dnd-kit/sortable";
|
|
1541
1711
|
import { CSS } from "@dnd-kit/utilities";
|
|
1712
|
+
import {
|
|
1713
|
+
Type,
|
|
1714
|
+
AlignLeft,
|
|
1715
|
+
Hash,
|
|
1716
|
+
Mail,
|
|
1717
|
+
Calendar,
|
|
1718
|
+
CheckSquare,
|
|
1719
|
+
ChevronDown as ChevronDown2,
|
|
1720
|
+
Circle,
|
|
1721
|
+
Upload,
|
|
1722
|
+
Link,
|
|
1723
|
+
Braces,
|
|
1724
|
+
X as X2
|
|
1725
|
+
} from "lucide-react";
|
|
1542
1726
|
var ICON_MAP = {
|
|
1543
|
-
text:
|
|
1544
|
-
textarea:
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
upload:
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
point: "P",
|
|
1555
|
-
relationship: "->>",
|
|
1556
|
-
array: "[]",
|
|
1557
|
-
group: "{ }",
|
|
1558
|
-
json: "{ }",
|
|
1559
|
-
ui: "UI"
|
|
1727
|
+
text: Type,
|
|
1728
|
+
textarea: AlignLeft,
|
|
1729
|
+
number: Hash,
|
|
1730
|
+
email: Mail,
|
|
1731
|
+
date: Calendar,
|
|
1732
|
+
checkbox: CheckSquare,
|
|
1733
|
+
select: ChevronDown2,
|
|
1734
|
+
radio: Circle,
|
|
1735
|
+
upload: Upload,
|
|
1736
|
+
relationship: Link,
|
|
1737
|
+
json: Braces
|
|
1560
1738
|
};
|
|
1561
1739
|
function SortableFieldCard({ field, blockId, index }) {
|
|
1562
1740
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
@@ -1565,6 +1743,7 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1565
1743
|
const activeFieldId = useBuilderStore((s) => s.activeFieldId);
|
|
1566
1744
|
const setActiveField = useBuilderStore((s) => s.setActiveField);
|
|
1567
1745
|
const removeField = useBuilderStore((s) => s.removeField);
|
|
1746
|
+
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
1568
1747
|
const isActive = activeFieldId === field.id;
|
|
1569
1748
|
const wrapStyle = {
|
|
1570
1749
|
transform: CSS.Transform.toString(transform),
|
|
@@ -1585,10 +1764,10 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1585
1764
|
className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
|
|
1586
1765
|
onClick: () => setActiveField(isActive ? null : field.id)
|
|
1587
1766
|
},
|
|
1588
|
-
/* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__icon" },
|
|
1767
|
+
/* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ React9.createElement(FieldIcon, { type: field.type })),
|
|
1589
1768
|
/* @__PURE__ */ React9.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ React9.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ React9.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__required" }, "*"))),
|
|
1590
1769
|
/* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
|
|
1591
|
-
/* @__PURE__ */ React9.createElement(
|
|
1770
|
+
!isReadOnly && /* @__PURE__ */ React9.createElement(
|
|
1592
1771
|
"button",
|
|
1593
1772
|
{
|
|
1594
1773
|
type: "button",
|
|
@@ -1600,11 +1779,15 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1600
1779
|
className: "bb-field-card__delete",
|
|
1601
1780
|
title: "Remove field"
|
|
1602
1781
|
},
|
|
1603
|
-
|
|
1782
|
+
/* @__PURE__ */ React9.createElement(X2, { size: 12, strokeWidth: 2 })
|
|
1604
1783
|
)
|
|
1605
1784
|
)
|
|
1606
1785
|
);
|
|
1607
1786
|
}
|
|
1787
|
+
function FieldIcon({ type }) {
|
|
1788
|
+
const Icon = ICON_MAP[type];
|
|
1789
|
+
return Icon ? /* @__PURE__ */ React9.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
|
|
1790
|
+
}
|
|
1608
1791
|
|
|
1609
1792
|
// src/block-builder/components/canvas/BuilderCanvas.tsx
|
|
1610
1793
|
function BuilderCanvas() {
|
|
@@ -1721,21 +1904,15 @@ import React12 from "react";
|
|
|
1721
1904
|
var ALL_TYPES = [
|
|
1722
1905
|
"text",
|
|
1723
1906
|
"textarea",
|
|
1724
|
-
"richText",
|
|
1725
1907
|
"number",
|
|
1908
|
+
"email",
|
|
1909
|
+
"date",
|
|
1726
1910
|
"checkbox",
|
|
1727
1911
|
"select",
|
|
1728
1912
|
"radio",
|
|
1729
|
-
"date",
|
|
1730
1913
|
"upload",
|
|
1731
|
-
"email",
|
|
1732
|
-
"code",
|
|
1733
|
-
"point",
|
|
1734
1914
|
"relationship",
|
|
1735
|
-
"
|
|
1736
|
-
"group",
|
|
1737
|
-
"json",
|
|
1738
|
-
"ui"
|
|
1915
|
+
"json"
|
|
1739
1916
|
];
|
|
1740
1917
|
function FieldConfig() {
|
|
1741
1918
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
@@ -1908,19 +2085,20 @@ function ConfigPanel() {
|
|
|
1908
2085
|
// src/block-builder/components/sidebar/FieldPalette.tsx
|
|
1909
2086
|
import React14, { useState as useState6 } from "react";
|
|
1910
2087
|
import {
|
|
1911
|
-
Type,
|
|
1912
|
-
AlignLeft,
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2088
|
+
Type as Type2,
|
|
2089
|
+
AlignLeft as AlignLeft2,
|
|
2090
|
+
AlignJustify,
|
|
2091
|
+
Hash as Hash2,
|
|
2092
|
+
Mail as Mail2,
|
|
2093
|
+
Calendar as Calendar2,
|
|
2094
|
+
CheckSquare as CheckSquare2,
|
|
2095
|
+
ChevronDown as ChevronDown3,
|
|
2096
|
+
Circle as Circle2,
|
|
2097
|
+
Upload as Upload2,
|
|
2098
|
+
Link as Link2,
|
|
1921
2099
|
List,
|
|
1922
2100
|
Folder,
|
|
1923
|
-
Braces
|
|
2101
|
+
Braces as Braces2
|
|
1924
2102
|
} from "lucide-react";
|
|
1925
2103
|
|
|
1926
2104
|
// src/block-builder/lib/field-palette.ts
|
|
@@ -1955,19 +2133,20 @@ function getFieldMeta(type) {
|
|
|
1955
2133
|
|
|
1956
2134
|
// src/block-builder/components/sidebar/FieldPalette.tsx
|
|
1957
2135
|
var ICON_MAP2 = {
|
|
1958
|
-
Type,
|
|
1959
|
-
AlignLeft,
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2136
|
+
Type: Type2,
|
|
2137
|
+
AlignLeft: AlignLeft2,
|
|
2138
|
+
AlignJustify,
|
|
2139
|
+
Hash: Hash2,
|
|
2140
|
+
Mail: Mail2,
|
|
2141
|
+
Calendar: Calendar2,
|
|
2142
|
+
CheckSquare: CheckSquare2,
|
|
2143
|
+
ChevronDown: ChevronDown3,
|
|
2144
|
+
Circle: Circle2,
|
|
2145
|
+
Upload: Upload2,
|
|
2146
|
+
Link: Link2,
|
|
1968
2147
|
List,
|
|
1969
2148
|
Folder,
|
|
1970
|
-
Braces
|
|
2149
|
+
Braces: Braces2
|
|
1971
2150
|
};
|
|
1972
2151
|
function FieldPalette() {
|
|
1973
2152
|
const [search, setSearch] = useState6("");
|
|
@@ -2040,7 +2219,7 @@ function FieldButton({
|
|
|
2040
2219
|
}
|
|
2041
2220
|
|
|
2042
2221
|
// src/block-builder/components/canvas/CodePreview.tsx
|
|
2043
|
-
import React15, { useCallback as useCallback4, useEffect as
|
|
2222
|
+
import React15, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState7 } from "react";
|
|
2044
2223
|
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2045
2224
|
"import",
|
|
2046
2225
|
"export",
|
|
@@ -2135,8 +2314,8 @@ function CodePreview() {
|
|
|
2135
2314
|
const [fileMap, setFileMap] = useState7({});
|
|
2136
2315
|
const [activeFile, setActiveFile] = useState7(null);
|
|
2137
2316
|
const [copied, setCopied] = useState7(false);
|
|
2138
|
-
const timerRef =
|
|
2139
|
-
const copyTimerRef =
|
|
2317
|
+
const timerRef = useRef5(null);
|
|
2318
|
+
const copyTimerRef = useRef5(null);
|
|
2140
2319
|
const regenerate = useCallback4(() => {
|
|
2141
2320
|
if (blocks.length === 0) {
|
|
2142
2321
|
setFileMap({});
|
|
@@ -2154,7 +2333,7 @@ function CodePreview() {
|
|
|
2154
2333
|
return prev && keys.includes(prev) ? prev : keys[0] ?? null;
|
|
2155
2334
|
});
|
|
2156
2335
|
}, [blocks]);
|
|
2157
|
-
|
|
2336
|
+
useEffect5(() => {
|
|
2158
2337
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2159
2338
|
timerRef.current = setTimeout(regenerate, 300);
|
|
2160
2339
|
return () => {
|
|
@@ -2309,18 +2488,20 @@ function BuilderShell({ loadSlug }) {
|
|
|
2309
2488
|
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
2310
2489
|
const [activeSlug, setActiveSlug] = useState8(loadSlug ?? null);
|
|
2311
2490
|
const [loading, setLoading] = useState8(!!loadSlug);
|
|
2312
|
-
const [
|
|
2491
|
+
const [notification, setNotification] = useState8(null);
|
|
2313
2492
|
const [showCodePreview, setShowCodePreview] = useState8(false);
|
|
2314
2493
|
const [versions, setVersions] = useState8([]);
|
|
2315
2494
|
const [selectedVersionId, setSelectedVersionId] = useState8(null);
|
|
2316
2495
|
const [blockDefs, setBlockDefs] = useState8([]);
|
|
2317
2496
|
const [mobilePanelTab, setMobilePanelTab] = useState8("blocks");
|
|
2318
|
-
|
|
2497
|
+
useEffect6(() => {
|
|
2319
2498
|
fetch("/api/block-definitions?limit=200&depth=0").then((r) => r.json()).then((json) => {
|
|
2320
2499
|
setBlockDefs(
|
|
2321
2500
|
(json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
|
|
2322
2501
|
);
|
|
2323
|
-
}).catch(() => {
|
|
2502
|
+
}).catch((err) => {
|
|
2503
|
+
console.error("[block-builder] Failed to load block definitions:", err);
|
|
2504
|
+
setNotification({ status: "error", title: "Failed to load block definitions", errors: ["Could not load block definitions. Please refresh the page."] });
|
|
2324
2505
|
});
|
|
2325
2506
|
}, []);
|
|
2326
2507
|
const loadVersionsForSlug = useCallback5(async (slug) => {
|
|
@@ -2334,7 +2515,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2334
2515
|
}, []);
|
|
2335
2516
|
const loadVersion = useCallback5(async (slug, versionId) => {
|
|
2336
2517
|
setLoading(true);
|
|
2337
|
-
|
|
2518
|
+
setNotification(null);
|
|
2338
2519
|
const url = versionId ? `/api/block-builder/load/${encodeURIComponent(slug)}?versionId=${encodeURIComponent(versionId)}` : `/api/block-builder/load/${encodeURIComponent(slug)}`;
|
|
2339
2520
|
try {
|
|
2340
2521
|
const res = await fetch(url);
|
|
@@ -2344,10 +2525,10 @@ function BuilderShell({ loadSlug }) {
|
|
|
2344
2525
|
setVersionMeta(json.versionId ?? null, !(json.isCurrent ?? true));
|
|
2345
2526
|
setSelectedVersionId(json.versionId ?? null);
|
|
2346
2527
|
} else {
|
|
2347
|
-
|
|
2528
|
+
setNotification({ status: "error", title: "Failed to load block", errors: [json.error ?? "Unknown error"] });
|
|
2348
2529
|
}
|
|
2349
2530
|
} catch (err) {
|
|
2350
|
-
|
|
2531
|
+
setNotification({ status: "error", title: "Network error", errors: [err instanceof Error ? err.message : "Could not reach the server."] });
|
|
2351
2532
|
} finally {
|
|
2352
2533
|
setLoading(false);
|
|
2353
2534
|
}
|
|
@@ -2357,7 +2538,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2357
2538
|
setBlockSlug(slug);
|
|
2358
2539
|
setVersions([]);
|
|
2359
2540
|
setSelectedVersionId(null);
|
|
2360
|
-
|
|
2541
|
+
setNotification(null);
|
|
2361
2542
|
setMobilePanelTab("canvas");
|
|
2362
2543
|
await loadVersion(slug);
|
|
2363
2544
|
const list = await loadVersionsForSlug(slug);
|
|
@@ -2365,7 +2546,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2365
2546
|
const current = list.find((v) => v.isCurrent) ?? list[0];
|
|
2366
2547
|
if (current) setSelectedVersionId(current.id);
|
|
2367
2548
|
}, [loadVersion, loadVersionsForSlug, setBlockSlug]);
|
|
2368
|
-
|
|
2549
|
+
useEffect6(() => {
|
|
2369
2550
|
if (!loadSlug) return;
|
|
2370
2551
|
loadBlockBySlug(loadSlug);
|
|
2371
2552
|
}, [loadSlug]);
|
|
@@ -2398,9 +2579,11 @@ function BuilderShell({ loadSlug }) {
|
|
|
2398
2579
|
selectedVersionId,
|
|
2399
2580
|
onVersionSelect: handleVersionSelect,
|
|
2400
2581
|
onRestoreVersion: handleRestoreVersion,
|
|
2401
|
-
onAfterPublish: handleAfterPublish
|
|
2582
|
+
onAfterPublish: handleAfterPublish,
|
|
2583
|
+
notification,
|
|
2584
|
+
onSetNotification: setNotification
|
|
2402
2585
|
}
|
|
2403
|
-
), loading && /* @__PURE__ */ React16.createElement("div", { className: "bb-loading-bar" }, "Loading..."),
|
|
2586
|
+
), loading && /* @__PURE__ */ React16.createElement("div", { className: "bb-loading-bar" }, "Loading..."), isReadOnly && !loading && /* @__PURE__ */ React16.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ React16.createElement("span", { className: "bb-readonly-banner__icon" }, "[i]"), /* @__PURE__ */ React16.createElement("span", null, "You are viewing a previous version - read only.", /* @__PURE__ */ React16.createElement(
|
|
2404
2587
|
"button",
|
|
2405
2588
|
{
|
|
2406
2589
|
type: "button",
|
|
@@ -2408,7 +2591,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2408
2591
|
onClick: handleRestoreVersion
|
|
2409
2592
|
},
|
|
2410
2593
|
"Switch to latest"
|
|
2411
|
-
))), /* @__PURE__ */ React16.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ React16.createElement(BlockList,
|
|
2594
|
+
))), /* @__PURE__ */ React16.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ React16.createElement(BlockList, { blockDefs, activeSlug, onBlockSelect: loadBlockBySlug }), /* @__PURE__ */ React16.createElement("div", { className: "bb-main__center" }, /* @__PURE__ */ React16.createElement(FieldPalette, null), /* @__PURE__ */ React16.createElement(BuilderCanvas, null)), /* @__PURE__ */ React16.createElement(ConfigPanel, null)), /* @__PURE__ */ React16.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ React16.createElement(
|
|
2412
2595
|
"button",
|
|
2413
2596
|
{
|
|
2414
2597
|
type: "button",
|
|
@@ -2437,10 +2620,10 @@ function BuilderShell({ loadSlug }) {
|
|
|
2437
2620
|
|
|
2438
2621
|
// src/components/BlockBuilderNavLink/index.tsx
|
|
2439
2622
|
import React17 from "react";
|
|
2440
|
-
import
|
|
2623
|
+
import Link3 from "next/link";
|
|
2441
2624
|
function BlockBuilderNavLink() {
|
|
2442
2625
|
return /* @__PURE__ */ React17.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ React17.createElement(
|
|
2443
|
-
|
|
2626
|
+
Link3,
|
|
2444
2627
|
{
|
|
2445
2628
|
href: "/block-builder",
|
|
2446
2629
|
target: "_blank",
|
|
@@ -2465,6 +2648,7 @@ function BlockBuilderNavLink() {
|
|
|
2465
2648
|
export {
|
|
2466
2649
|
BlockBuilderNavLink,
|
|
2467
2650
|
BlockDataField,
|
|
2651
|
+
BlockVersionSync,
|
|
2468
2652
|
BuilderShell,
|
|
2469
2653
|
EditInBuilderButton,
|
|
2470
2654
|
SchemaBuilderField
|