@nextbridgehq/payload-block-builder 0.1.8 → 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 +199 -62
- package/dist/client.cjs +401 -283
- package/dist/client.d.cts +5 -1
- package/dist/client.d.ts +5 -1
- package/dist/client.js +202 -85
- package/dist/index.cjs +14 -1
- package/dist/index.js +14 -1
- package/package.json +1 -1
- package/src/components/BlockDataField/BlockDataField.css +170 -0
package/dist/client.d.cts
CHANGED
|
@@ -4,6 +4,10 @@ declare function BlockDataField({ path }: {
|
|
|
4
4
|
path: string;
|
|
5
5
|
}): React.JSX.Element;
|
|
6
6
|
|
|
7
|
+
declare function BlockVersionSync({ path }: {
|
|
8
|
+
path: string;
|
|
9
|
+
}): any;
|
|
10
|
+
|
|
7
11
|
type Props$1 = {
|
|
8
12
|
path: string;
|
|
9
13
|
readOnly?: boolean;
|
|
@@ -19,4 +23,4 @@ declare function BuilderShell({ loadSlug }: Props): React.JSX.Element;
|
|
|
19
23
|
|
|
20
24
|
declare function BlockBuilderNavLink(): React.JSX.Element;
|
|
21
25
|
|
|
22
|
-
export { BlockBuilderNavLink, BlockDataField, BuilderShell, EditInBuilderButton, SchemaBuilderField };
|
|
26
|
+
export { BlockBuilderNavLink, BlockDataField, BlockVersionSync, BuilderShell, EditInBuilderButton, SchemaBuilderField };
|
package/dist/client.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ declare function BlockDataField({ path }: {
|
|
|
4
4
|
path: string;
|
|
5
5
|
}): React.JSX.Element;
|
|
6
6
|
|
|
7
|
+
declare function BlockVersionSync({ path }: {
|
|
8
|
+
path: string;
|
|
9
|
+
}): any;
|
|
10
|
+
|
|
7
11
|
type Props$1 = {
|
|
8
12
|
path: string;
|
|
9
13
|
readOnly?: boolean;
|
|
@@ -19,4 +23,4 @@ declare function BuilderShell({ loadSlug }: Props): React.JSX.Element;
|
|
|
19
23
|
|
|
20
24
|
declare function BlockBuilderNavLink(): React.JSX.Element;
|
|
21
25
|
|
|
22
|
-
export { BlockBuilderNavLink, BlockDataField, BuilderShell, EditInBuilderButton, SchemaBuilderField };
|
|
26
|
+
export { BlockBuilderNavLink, BlockDataField, BlockVersionSync, BuilderShell, EditInBuilderButton, SchemaBuilderField };
|
package/dist/client.js
CHANGED
|
@@ -39,55 +39,132 @@ 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
|
|
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 }) {
|
|
43
53
|
const changeRef = useRef(onChange);
|
|
44
54
|
const closeRef = useRef(() => {
|
|
45
55
|
});
|
|
46
56
|
useEffect(() => {
|
|
47
57
|
changeRef.current = onChange;
|
|
48
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
|
+
});
|
|
49
67
|
const handleSelect = useCallback(
|
|
50
68
|
({ docID, doc }) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
78
|
},
|
|
57
|
-
[]
|
|
79
|
+
[hasMany]
|
|
58
80
|
);
|
|
59
|
-
const [ListDrawer, ListDrawerToggler, { closeDrawer }] = useListDrawer({
|
|
81
|
+
const [ListDrawer, ListDrawerToggler, { closeDrawer, openDrawer }] = useListDrawer({
|
|
60
82
|
collectionSlugs: [collection]
|
|
61
83
|
});
|
|
62
84
|
closeRef.current = closeDrawer;
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const relTitle = relObj?.title ? String(relObj.title) : null;
|
|
66
|
-
const [fetchedTitle, setFetchedTitle] = useState(null);
|
|
85
|
+
const [fetchedTitles, setFetchedTitles] = useState({});
|
|
86
|
+
const fetchingRef = useRef(/* @__PURE__ */ new Set());
|
|
67
87
|
useEffect(() => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
});
|
|
78
103
|
});
|
|
79
|
-
}, [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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",
|
|
83
115
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
onClick:
|
|
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
|
+
}
|
|
88
123
|
},
|
|
89
|
-
"
|
|
90
|
-
|
|
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."));
|
|
91
168
|
}
|
|
92
169
|
function SchemaForm({ schema, value, onChange }) {
|
|
93
170
|
const set = useCallback(
|
|
@@ -114,7 +191,7 @@ function FieldInput({ field, value, onChange }) {
|
|
|
114
191
|
"input",
|
|
115
192
|
{
|
|
116
193
|
className: "bdf-input",
|
|
117
|
-
type: field.type === "email" ? "email" : "text",
|
|
194
|
+
type: field.type === "email" ? "email" : field.type === "url" ? "url" : "text",
|
|
118
195
|
value: value ?? "",
|
|
119
196
|
onChange: (e) => onChange(e.target.value)
|
|
120
197
|
}
|
|
@@ -167,7 +244,7 @@ function FieldInput({ field, value, onChange }) {
|
|
|
167
244
|
className: "bdf-input",
|
|
168
245
|
type: "number",
|
|
169
246
|
value: numVal,
|
|
170
|
-
onChange: (e) => onChange(e.target.value === "" ?
|
|
247
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.valueAsNumber)
|
|
171
248
|
}
|
|
172
249
|
));
|
|
173
250
|
}
|
|
@@ -227,33 +304,24 @@ function FieldInput({ field, value, onChange }) {
|
|
|
227
304
|
}
|
|
228
305
|
);
|
|
229
306
|
case "relationship": {
|
|
230
|
-
const
|
|
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
|
+
}
|
|
231
311
|
return /* @__PURE__ */ React.createElement(
|
|
232
312
|
RelationshipPicker,
|
|
233
313
|
{
|
|
234
314
|
label,
|
|
235
315
|
required: field.required,
|
|
236
|
-
collection,
|
|
316
|
+
collection: relField.collection,
|
|
317
|
+
hasMany: relField.hasMany ?? false,
|
|
237
318
|
value,
|
|
238
319
|
onChange
|
|
239
320
|
}
|
|
240
321
|
);
|
|
241
322
|
}
|
|
242
323
|
case "json":
|
|
243
|
-
return /* @__PURE__ */ React.createElement(
|
|
244
|
-
"textarea",
|
|
245
|
-
{
|
|
246
|
-
className: "bdf-input bdf-textarea bdf-mono",
|
|
247
|
-
value: value !== void 0 ? JSON.stringify(value, null, 2) : "",
|
|
248
|
-
rows: 4,
|
|
249
|
-
onChange: (e) => {
|
|
250
|
-
try {
|
|
251
|
-
onChange(JSON.parse(e.target.value));
|
|
252
|
-
} catch {
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
));
|
|
324
|
+
return /* @__PURE__ */ React.createElement(JsonField, { label, required: field.required, value, onChange });
|
|
257
325
|
case "array": {
|
|
258
326
|
const rows = Array.isArray(value) ? value : [];
|
|
259
327
|
const subFields = field.fields ?? [];
|
|
@@ -349,9 +417,37 @@ function BlockDataField({ path }) {
|
|
|
349
417
|
)));
|
|
350
418
|
}
|
|
351
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
|
+
|
|
352
448
|
// src/components/SchemaBuilderField/index.tsx
|
|
353
|
-
import React5, { useCallback as useCallback3, useEffect as
|
|
354
|
-
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";
|
|
355
451
|
|
|
356
452
|
// src/components/SchemaBuilderField/FieldRow.tsx
|
|
357
453
|
import React4, { useState as useState2 } from "react";
|
|
@@ -925,22 +1021,22 @@ function parseSchema(raw) {
|
|
|
925
1021
|
return [];
|
|
926
1022
|
}
|
|
927
1023
|
function SchemaBuilderField({ path, readOnly }) {
|
|
928
|
-
const { value, setValue } =
|
|
929
|
-
const setValueRef =
|
|
930
|
-
|
|
1024
|
+
const { value, setValue } = useField3({ path });
|
|
1025
|
+
const setValueRef = useRef3(setValue);
|
|
1026
|
+
useEffect3(() => {
|
|
931
1027
|
setValueRef.current = setValue;
|
|
932
1028
|
});
|
|
933
1029
|
const [fields, setFields] = useState3(() => parseSchema(value));
|
|
934
1030
|
const hasExistingValue = value !== void 0 && value !== null;
|
|
935
1031
|
const [hydrated, setHydrated] = useState3(!hasExistingValue);
|
|
936
|
-
|
|
1032
|
+
useEffect3(() => {
|
|
937
1033
|
if (hydrated) return;
|
|
938
1034
|
if (value !== void 0 && value !== null) {
|
|
939
1035
|
setFields(parseSchema(value));
|
|
940
1036
|
setHydrated(true);
|
|
941
1037
|
}
|
|
942
1038
|
}, [value, hydrated]);
|
|
943
|
-
|
|
1039
|
+
useEffect3(() => {
|
|
944
1040
|
if (!hydrated) return;
|
|
945
1041
|
setValueRef.current({ fields });
|
|
946
1042
|
}, [fields, hydrated]);
|
|
@@ -997,10 +1093,10 @@ function SchemaBuilderField({ path, readOnly }) {
|
|
|
997
1093
|
// src/components/EditInBuilderButton/index.tsx
|
|
998
1094
|
import React6 from "react";
|
|
999
1095
|
import { useDocumentInfo } from "@payloadcms/ui";
|
|
1000
|
-
import { useField as
|
|
1096
|
+
import { useField as useField4 } from "@payloadcms/ui";
|
|
1001
1097
|
function EditInBuilderButton() {
|
|
1002
1098
|
const { id } = useDocumentInfo();
|
|
1003
|
-
const { value: slug } =
|
|
1099
|
+
const { value: slug } = useField4({ path: "slug" });
|
|
1004
1100
|
if (!id || !slug) return null;
|
|
1005
1101
|
return /* @__PURE__ */ React6.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ React6.createElement(
|
|
1006
1102
|
"a",
|
|
@@ -1028,7 +1124,7 @@ function EditInBuilderButton() {
|
|
|
1028
1124
|
}
|
|
1029
1125
|
|
|
1030
1126
|
// src/block-builder/components/canvas/BuilderShell.tsx
|
|
1031
|
-
import React16, { useCallback as useCallback5, useEffect as
|
|
1127
|
+
import React16, { useCallback as useCallback5, useEffect as useEffect6, useState as useState8 } from "react";
|
|
1032
1128
|
|
|
1033
1129
|
// src/block-builder/store/builder.store.ts
|
|
1034
1130
|
import { create } from "zustand";
|
|
@@ -1185,7 +1281,7 @@ var useBuilderStore = create()(
|
|
|
1185
1281
|
);
|
|
1186
1282
|
|
|
1187
1283
|
// src/block-builder/components/canvas/TopBar.tsx
|
|
1188
|
-
import React7, { useEffect as
|
|
1284
|
+
import React7, { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
|
|
1189
1285
|
import { Blocks, ChevronDown, X } from "lucide-react";
|
|
1190
1286
|
|
|
1191
1287
|
// src/block-builder/lib/mapToSaveRequest.ts
|
|
@@ -1340,7 +1436,7 @@ function generateIndexFile(blocks) {
|
|
|
1340
1436
|
}
|
|
1341
1437
|
|
|
1342
1438
|
// src/block-builder/components/canvas/TopBar.tsx
|
|
1343
|
-
function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish }) {
|
|
1439
|
+
function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish, notification, onSetNotification }) {
|
|
1344
1440
|
const blocks = useBuilderStore((s) => s.blocks);
|
|
1345
1441
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
1346
1442
|
const activeBlock = blocks.find((b) => b.id === activeBlockId);
|
|
@@ -1348,21 +1444,21 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1348
1444
|
const isDirty = useBuilderStore((s) => s.isDirty);
|
|
1349
1445
|
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
1350
1446
|
const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
|
|
1351
|
-
const
|
|
1447
|
+
const setNotification = onSetNotification;
|
|
1352
1448
|
const [versionDropdownOpen, setVersionDropdownOpen] = useState4(false);
|
|
1353
1449
|
const [blockPickerOpen, setBlockPickerOpen] = useState4(false);
|
|
1354
|
-
const dropdownRef =
|
|
1355
|
-
const blockPickerRef =
|
|
1450
|
+
const dropdownRef = useRef4(null);
|
|
1451
|
+
const blockPickerRef = useRef4(null);
|
|
1356
1452
|
const selectedVersion = versions.find((v) => v.id === selectedVersionId);
|
|
1357
1453
|
const currentVersion = versions.find((v) => v.isCurrent);
|
|
1358
1454
|
const activeBlockDef = blockDefs.find((b) => b.slug === activeSlug);
|
|
1359
|
-
|
|
1455
|
+
useEffect4(() => {
|
|
1360
1456
|
if (notification?.status === "success") {
|
|
1361
1457
|
const t = setTimeout(() => setNotification(null), 3e3);
|
|
1362
1458
|
return () => clearTimeout(t);
|
|
1363
1459
|
}
|
|
1364
1460
|
}, [notification]);
|
|
1365
|
-
|
|
1461
|
+
useEffect4(() => {
|
|
1366
1462
|
function handleClick(e) {
|
|
1367
1463
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
1368
1464
|
setVersionDropdownOpen(false);
|
|
@@ -1525,14 +1621,32 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1525
1621
|
// src/block-builder/components/canvas/BlockList.tsx
|
|
1526
1622
|
import React8 from "react";
|
|
1527
1623
|
import { Copy, Trash2, Plus } from "lucide-react";
|
|
1528
|
-
function BlockList() {
|
|
1624
|
+
function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
|
|
1529
1625
|
const blocks = useBuilderStore((s) => s.blocks);
|
|
1530
1626
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
1531
1627
|
const addBlock = useBuilderStore((s) => s.addBlock);
|
|
1532
1628
|
const removeBlock = useBuilderStore((s) => s.removeBlock);
|
|
1533
1629
|
const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
|
|
1534
1630
|
const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
|
|
1535
|
-
|
|
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) => {
|
|
1536
1650
|
const isActive = block.id === activeBlockId;
|
|
1537
1651
|
return /* @__PURE__ */ React8.createElement(
|
|
1538
1652
|
"div",
|
|
@@ -1571,7 +1685,7 @@ function BlockList() {
|
|
|
1571
1685
|
)
|
|
1572
1686
|
)
|
|
1573
1687
|
);
|
|
1574
|
-
})));
|
|
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.")));
|
|
1575
1689
|
}
|
|
1576
1690
|
|
|
1577
1691
|
// src/block-builder/components/canvas/BuilderCanvas.tsx
|
|
@@ -2105,7 +2219,7 @@ function FieldButton({
|
|
|
2105
2219
|
}
|
|
2106
2220
|
|
|
2107
2221
|
// src/block-builder/components/canvas/CodePreview.tsx
|
|
2108
|
-
import React15, { useCallback as useCallback4, useEffect as
|
|
2222
|
+
import React15, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState7 } from "react";
|
|
2109
2223
|
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2110
2224
|
"import",
|
|
2111
2225
|
"export",
|
|
@@ -2200,8 +2314,8 @@ function CodePreview() {
|
|
|
2200
2314
|
const [fileMap, setFileMap] = useState7({});
|
|
2201
2315
|
const [activeFile, setActiveFile] = useState7(null);
|
|
2202
2316
|
const [copied, setCopied] = useState7(false);
|
|
2203
|
-
const timerRef =
|
|
2204
|
-
const copyTimerRef =
|
|
2317
|
+
const timerRef = useRef5(null);
|
|
2318
|
+
const copyTimerRef = useRef5(null);
|
|
2205
2319
|
const regenerate = useCallback4(() => {
|
|
2206
2320
|
if (blocks.length === 0) {
|
|
2207
2321
|
setFileMap({});
|
|
@@ -2219,7 +2333,7 @@ function CodePreview() {
|
|
|
2219
2333
|
return prev && keys.includes(prev) ? prev : keys[0] ?? null;
|
|
2220
2334
|
});
|
|
2221
2335
|
}, [blocks]);
|
|
2222
|
-
|
|
2336
|
+
useEffect5(() => {
|
|
2223
2337
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2224
2338
|
timerRef.current = setTimeout(regenerate, 300);
|
|
2225
2339
|
return () => {
|
|
@@ -2374,20 +2488,20 @@ function BuilderShell({ loadSlug }) {
|
|
|
2374
2488
|
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
2375
2489
|
const [activeSlug, setActiveSlug] = useState8(loadSlug ?? null);
|
|
2376
2490
|
const [loading, setLoading] = useState8(!!loadSlug);
|
|
2377
|
-
const [
|
|
2491
|
+
const [notification, setNotification] = useState8(null);
|
|
2378
2492
|
const [showCodePreview, setShowCodePreview] = useState8(false);
|
|
2379
2493
|
const [versions, setVersions] = useState8([]);
|
|
2380
2494
|
const [selectedVersionId, setSelectedVersionId] = useState8(null);
|
|
2381
2495
|
const [blockDefs, setBlockDefs] = useState8([]);
|
|
2382
2496
|
const [mobilePanelTab, setMobilePanelTab] = useState8("blocks");
|
|
2383
|
-
|
|
2497
|
+
useEffect6(() => {
|
|
2384
2498
|
fetch("/api/block-definitions?limit=200&depth=0").then((r) => r.json()).then((json) => {
|
|
2385
2499
|
setBlockDefs(
|
|
2386
2500
|
(json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
|
|
2387
2501
|
);
|
|
2388
2502
|
}).catch((err) => {
|
|
2389
2503
|
console.error("[block-builder] Failed to load block definitions:", err);
|
|
2390
|
-
|
|
2504
|
+
setNotification({ status: "error", title: "Failed to load block definitions", errors: ["Could not load block definitions. Please refresh the page."] });
|
|
2391
2505
|
});
|
|
2392
2506
|
}, []);
|
|
2393
2507
|
const loadVersionsForSlug = useCallback5(async (slug) => {
|
|
@@ -2401,7 +2515,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2401
2515
|
}, []);
|
|
2402
2516
|
const loadVersion = useCallback5(async (slug, versionId) => {
|
|
2403
2517
|
setLoading(true);
|
|
2404
|
-
|
|
2518
|
+
setNotification(null);
|
|
2405
2519
|
const url = versionId ? `/api/block-builder/load/${encodeURIComponent(slug)}?versionId=${encodeURIComponent(versionId)}` : `/api/block-builder/load/${encodeURIComponent(slug)}`;
|
|
2406
2520
|
try {
|
|
2407
2521
|
const res = await fetch(url);
|
|
@@ -2411,10 +2525,10 @@ function BuilderShell({ loadSlug }) {
|
|
|
2411
2525
|
setVersionMeta(json.versionId ?? null, !(json.isCurrent ?? true));
|
|
2412
2526
|
setSelectedVersionId(json.versionId ?? null);
|
|
2413
2527
|
} else {
|
|
2414
|
-
|
|
2528
|
+
setNotification({ status: "error", title: "Failed to load block", errors: [json.error ?? "Unknown error"] });
|
|
2415
2529
|
}
|
|
2416
2530
|
} catch (err) {
|
|
2417
|
-
|
|
2531
|
+
setNotification({ status: "error", title: "Network error", errors: [err instanceof Error ? err.message : "Could not reach the server."] });
|
|
2418
2532
|
} finally {
|
|
2419
2533
|
setLoading(false);
|
|
2420
2534
|
}
|
|
@@ -2424,7 +2538,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2424
2538
|
setBlockSlug(slug);
|
|
2425
2539
|
setVersions([]);
|
|
2426
2540
|
setSelectedVersionId(null);
|
|
2427
|
-
|
|
2541
|
+
setNotification(null);
|
|
2428
2542
|
setMobilePanelTab("canvas");
|
|
2429
2543
|
await loadVersion(slug);
|
|
2430
2544
|
const list = await loadVersionsForSlug(slug);
|
|
@@ -2432,7 +2546,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2432
2546
|
const current = list.find((v) => v.isCurrent) ?? list[0];
|
|
2433
2547
|
if (current) setSelectedVersionId(current.id);
|
|
2434
2548
|
}, [loadVersion, loadVersionsForSlug, setBlockSlug]);
|
|
2435
|
-
|
|
2549
|
+
useEffect6(() => {
|
|
2436
2550
|
if (!loadSlug) return;
|
|
2437
2551
|
loadBlockBySlug(loadSlug);
|
|
2438
2552
|
}, [loadSlug]);
|
|
@@ -2465,9 +2579,11 @@ function BuilderShell({ loadSlug }) {
|
|
|
2465
2579
|
selectedVersionId,
|
|
2466
2580
|
onVersionSelect: handleVersionSelect,
|
|
2467
2581
|
onRestoreVersion: handleRestoreVersion,
|
|
2468
|
-
onAfterPublish: handleAfterPublish
|
|
2582
|
+
onAfterPublish: handleAfterPublish,
|
|
2583
|
+
notification,
|
|
2584
|
+
onSetNotification: setNotification
|
|
2469
2585
|
}
|
|
2470
|
-
), 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(
|
|
2471
2587
|
"button",
|
|
2472
2588
|
{
|
|
2473
2589
|
type: "button",
|
|
@@ -2475,7 +2591,7 @@ function BuilderShell({ loadSlug }) {
|
|
|
2475
2591
|
onClick: handleRestoreVersion
|
|
2476
2592
|
},
|
|
2477
2593
|
"Switch to latest"
|
|
2478
|
-
))), /* @__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(
|
|
2479
2595
|
"button",
|
|
2480
2596
|
{
|
|
2481
2597
|
type: "button",
|
|
@@ -2532,6 +2648,7 @@ function BlockBuilderNavLink() {
|
|
|
2532
2648
|
export {
|
|
2533
2649
|
BlockBuilderNavLink,
|
|
2534
2650
|
BlockDataField,
|
|
2651
|
+
BlockVersionSync,
|
|
2535
2652
|
BuilderShell,
|
|
2536
2653
|
EditInBuilderButton,
|
|
2537
2654
|
SchemaBuilderField
|
package/dist/index.cjs
CHANGED
|
@@ -164,13 +164,26 @@ function dbLayoutField(fieldName = "dbLayout", tabLabel = "DB Layout") {
|
|
|
164
164
|
type: "relationship",
|
|
165
165
|
relationTo: "block-definitions",
|
|
166
166
|
required: true,
|
|
167
|
-
admin: {
|
|
167
|
+
admin: {
|
|
168
|
+
description: "Which block type to use.",
|
|
169
|
+
width: "50%",
|
|
170
|
+
components: {
|
|
171
|
+
afterInput: ["@nextbridgehq/payload-block-builder/client#BlockVersionSync"]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
168
174
|
},
|
|
169
175
|
{
|
|
170
176
|
name: "blockVersion",
|
|
171
177
|
type: "relationship",
|
|
172
178
|
relationTo: "block-definition-versions",
|
|
173
179
|
required: true,
|
|
180
|
+
filterOptions: ({ siblingData }) => {
|
|
181
|
+
const def = siblingData?.blockDefinition;
|
|
182
|
+
if (!def) return false;
|
|
183
|
+
const defId = def && typeof def === "object" ? def.id : def;
|
|
184
|
+
if (!defId) return false;
|
|
185
|
+
return { blockDefinition: { equals: defId } };
|
|
186
|
+
},
|
|
174
187
|
admin: { description: "Which schema version to use.", width: "50%" }
|
|
175
188
|
}
|
|
176
189
|
]
|
package/dist/index.js
CHANGED
|
@@ -136,13 +136,26 @@ function dbLayoutField(fieldName = "dbLayout", tabLabel = "DB Layout") {
|
|
|
136
136
|
type: "relationship",
|
|
137
137
|
relationTo: "block-definitions",
|
|
138
138
|
required: true,
|
|
139
|
-
admin: {
|
|
139
|
+
admin: {
|
|
140
|
+
description: "Which block type to use.",
|
|
141
|
+
width: "50%",
|
|
142
|
+
components: {
|
|
143
|
+
afterInput: ["@nextbridgehq/payload-block-builder/client#BlockVersionSync"]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
140
146
|
},
|
|
141
147
|
{
|
|
142
148
|
name: "blockVersion",
|
|
143
149
|
type: "relationship",
|
|
144
150
|
relationTo: "block-definition-versions",
|
|
145
151
|
required: true,
|
|
152
|
+
filterOptions: ({ siblingData }) => {
|
|
153
|
+
const def = siblingData?.blockDefinition;
|
|
154
|
+
if (!def) return false;
|
|
155
|
+
const defId = def && typeof def === "object" ? def.id : def;
|
|
156
|
+
if (!defId) return false;
|
|
157
|
+
return { blockDefinition: { equals: defId } };
|
|
158
|
+
},
|
|
146
159
|
admin: { description: "Which schema version to use.", width: "50%" }
|
|
147
160
|
}
|
|
148
161
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextbridgehq/payload-block-builder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Block Builder for Payload CMS",
|
|
5
5
|
"keywords": ["payload", "payloadcms", "payload-plugin", "cms", "block-builder", "dynamic-blocks"],
|
|
6
6
|
"homepage": "https://github.com/nextbridgehq/block-builder",
|