@nextbridgehq/payload-block-builder 0.1.7 → 0.1.8
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 +75 -1
- package/dist/bin/init.js +79 -25
- package/dist/client.cjs +118 -65
- package/dist/client.js +142 -75
- package/dist/index.cjs +32 -15
- package/dist/index.js +32 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,6 +14,18 @@ A visual block builder plugin for Payload v3. Design your content blocks through
|
|
|
14
14
|
- **Evolving content schemas:** Roll out new block versions without breaking content that was built against older ones.
|
|
15
15
|
- **Headless frontends:** Fetch structured block data from the Payload API and render it with any framework.
|
|
16
16
|
|
|
17
|
+
## Database compatibility
|
|
18
|
+
|
|
19
|
+
Works with all Payload-supported databases — no direct SQL, no database-specific code:
|
|
20
|
+
|
|
21
|
+
| Database | Adapter |
|
|
22
|
+
|---|---|
|
|
23
|
+
| PostgreSQL / Supabase / Neon | `@payloadcms/db-postgres` |
|
|
24
|
+
| SQLite / Turso / LibSQL | `@payloadcms/db-sqlite` |
|
|
25
|
+
| MongoDB | `@payloadcms/db-mongodb` |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
17
29
|
## Quick start
|
|
18
30
|
|
|
19
31
|
### Option A — Automatic setup (recommended)
|
|
@@ -77,11 +89,19 @@ export default buildConfig({
|
|
|
77
89
|
|
|
78
90
|
```tsx
|
|
79
91
|
import React from 'react'
|
|
92
|
+
import { headers } from 'next/headers'
|
|
93
|
+
import { redirect } from 'next/navigation'
|
|
94
|
+
import { getPayload } from 'payload'
|
|
95
|
+
import config from '@payload-config'
|
|
80
96
|
import '@nextbridgehq/payload-block-builder/builder.css'
|
|
81
97
|
|
|
82
98
|
export const metadata = { title: 'Block Builder' }
|
|
83
99
|
|
|
84
|
-
export default function BlockBuilderLayout({ children }: { children: React.ReactNode }) {
|
|
100
|
+
export default async function BlockBuilderLayout({ children }: { children: React.ReactNode }) {
|
|
101
|
+
const payload = await getPayload({ config })
|
|
102
|
+
const { user } = await payload.auth({ headers: await headers() })
|
|
103
|
+
if (!user) redirect('/admin/login')
|
|
104
|
+
|
|
85
105
|
return (
|
|
86
106
|
<html lang="en">
|
|
87
107
|
<body style={{ margin: 0, padding: 0, height: '100vh', overflow: 'hidden' }}>
|
|
@@ -186,6 +206,60 @@ Render each block type however you like — a switch statement or a component ma
|
|
|
186
206
|
|
|
187
207
|
---
|
|
188
208
|
|
|
209
|
+
## Supported field types
|
|
210
|
+
|
|
211
|
+
These field types are available in the block builder and render correctly in the admin field UI:
|
|
212
|
+
|
|
213
|
+
| Type | Description |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `text` | Single-line text input |
|
|
216
|
+
| `textarea` | Multi-line text input |
|
|
217
|
+
| `number` | Numeric input |
|
|
218
|
+
| `email` | Email address |
|
|
219
|
+
| `date` | Date picker |
|
|
220
|
+
| `checkbox` | Boolean toggle |
|
|
221
|
+
| `select` | Dropdown with custom options |
|
|
222
|
+
| `radio` | Radio button group with custom options |
|
|
223
|
+
| `upload` | File / image picker (from the media collection) |
|
|
224
|
+
| `relationship` | Document picker from any collection |
|
|
225
|
+
| `json` | Raw JSON data |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Using `dbLayoutField` directly
|
|
230
|
+
|
|
231
|
+
If you prefer not to use the plugin's `collections` option, you can add the layout tab manually to any collection:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { dbLayoutField } from '@nextbridgehq/payload-block-builder'
|
|
235
|
+
|
|
236
|
+
export const Pages: CollectionConfig = {
|
|
237
|
+
slug: 'pages',
|
|
238
|
+
fields: [
|
|
239
|
+
{
|
|
240
|
+
type: 'tabs',
|
|
241
|
+
tabs: [
|
|
242
|
+
{ label: 'Content', fields: [] },
|
|
243
|
+
dbLayoutField(), // fieldName='dbLayout', tab label='DB Layout'
|
|
244
|
+
dbLayoutField('heroBlocks', 'Hero'), // custom field name and tab label
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## CSS imports reference
|
|
254
|
+
|
|
255
|
+
| Import path | Purpose |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `@nextbridgehq/payload-block-builder/builder.css` | Block Builder UI page styles |
|
|
258
|
+
| `@nextbridgehq/payload-block-builder/block-data-field.css` | DB Layout field styles in admin |
|
|
259
|
+
| `@nextbridgehq/payload-block-builder/schema-builder-field.css` | Schema Builder field styles in admin |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
189
263
|
## License
|
|
190
264
|
|
|
191
265
|
MIT © [Nextbridge](https://nextbridge.com)
|
package/dist/bin/init.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
2
|
|
|
4
3
|
// src/bin/init.ts
|
|
5
4
|
import fs from "fs";
|
|
@@ -13,13 +12,21 @@ export default function BlockBuilderPage() {
|
|
|
13
12
|
}
|
|
14
13
|
`;
|
|
15
14
|
var LAYOUT_CONTENT = `import React from 'react'
|
|
15
|
+
import { headers } from 'next/headers'
|
|
16
|
+
import { redirect } from 'next/navigation'
|
|
17
|
+
import { getPayload } from 'payload'
|
|
18
|
+
import config from '@payload-config'
|
|
16
19
|
import '@nextbridgehq/payload-block-builder/builder.css'
|
|
17
20
|
|
|
18
21
|
export const metadata = {
|
|
19
22
|
title: 'Block Builder',
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
export default function BlockBuilderLayout({ children }: { children: React.ReactNode }) {
|
|
25
|
+
export default async function BlockBuilderLayout({ children }: { children: React.ReactNode }) {
|
|
26
|
+
const payload = await getPayload({ config })
|
|
27
|
+
const { user } = await payload.auth({ headers: await headers() })
|
|
28
|
+
if (!user) redirect('/admin/login')
|
|
29
|
+
|
|
23
30
|
return (
|
|
24
31
|
<html lang="en">
|
|
25
32
|
<body style={{ margin: 0, padding: 0, height: '100vh', overflow: 'hidden' }}>
|
|
@@ -76,7 +83,7 @@ function addImport(content) {
|
|
|
76
83
|
while ((m = lastFromRegex.exec(content)) !== null) lastMatch = m;
|
|
77
84
|
if (!lastMatch) return newImport + "\n" + content;
|
|
78
85
|
const insertPos = lastMatch.index + lastMatch[0].length;
|
|
79
|
-
return content.slice(0, insertPos) + "\n" + newImport + content.slice(insertPos);
|
|
86
|
+
return content.slice(0, insertPos) + "\n" + newImport + "\n" + content.slice(insertPos);
|
|
80
87
|
}
|
|
81
88
|
function insertIntoPluginsArray(content, collectionsArg) {
|
|
82
89
|
const pluginsMatch = /\bplugins\s*:\s*\[/.exec(content);
|
|
@@ -84,13 +91,19 @@ function insertIntoPluginsArray(content, collectionsArg) {
|
|
|
84
91
|
const openPos = content.indexOf("[", pluginsMatch.index);
|
|
85
92
|
const closePos = findClosingBracket(content, openPos);
|
|
86
93
|
if (closePos === -1) return null;
|
|
94
|
+
const beforePlugins = content.slice(0, pluginsMatch.index);
|
|
95
|
+
const pluginsLineStart = beforePlugins.lastIndexOf("\n") + 1;
|
|
96
|
+
const outerIndent = content.slice(pluginsLineStart, pluginsMatch.index).match(/^([ \t]*)/)?.[1] ?? " ";
|
|
97
|
+
const entryIndent = outerIndent + " ";
|
|
87
98
|
const beforeClose = content.slice(0, closePos);
|
|
88
99
|
const prevNL = beforeClose.lastIndexOf("\n");
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
const isSingleLine = prevNL < openPos;
|
|
101
|
+
const newEntry = `${entryIndent}dynamicBlocksPlugin({ collections: [${collectionsArg}] }),`;
|
|
102
|
+
if (isSingleLine) {
|
|
103
|
+
return content.slice(0, openPos + 1) + "\n" + newEntry + "\n" + outerIndent + content.slice(closePos);
|
|
104
|
+
} else {
|
|
105
|
+
return content.slice(0, prevNL + 1) + newEntry + "\n" + content.slice(prevNL + 1);
|
|
106
|
+
}
|
|
94
107
|
}
|
|
95
108
|
function injectPluginsBlock(content, collectionsArg) {
|
|
96
109
|
const collMatch = /\bcollections\s*:\s*\[/.exec(content);
|
|
@@ -110,7 +123,13 @@ ${outerIndent}],`;
|
|
|
110
123
|
return content.slice(0, afterCollLine) + "\n" + pluginsBlock + content.slice(afterCollLine);
|
|
111
124
|
}
|
|
112
125
|
function modifyPayloadConfig(configPath, collectionsArg) {
|
|
113
|
-
let content
|
|
126
|
+
let content;
|
|
127
|
+
try {
|
|
128
|
+
content = fs.readFileSync(configPath, "utf8");
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(`Error: Could not read ${configPath}: ${err.message}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
114
133
|
if (content.includes("dynamicBlocksPlugin")) {
|
|
115
134
|
console.log(`Skipped: dynamicBlocksPlugin already present in ${configPath}`);
|
|
116
135
|
return;
|
|
@@ -119,8 +138,16 @@ function modifyPayloadConfig(configPath, collectionsArg) {
|
|
|
119
138
|
const noComments = content.replace(/\/\/[^\n]*/g, "");
|
|
120
139
|
const hasPluginsArray = /\bplugins\s*:\s*\[/.test(noComments);
|
|
121
140
|
const hasPluginsShorthand = /^\s*plugins\s*,/m.test(noComments);
|
|
141
|
+
function writeConfig(data) {
|
|
142
|
+
try {
|
|
143
|
+
fs.writeFileSync(configPath, data, "utf8");
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error(`Error: Could not write ${configPath}: ${err.message}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
122
149
|
if (hasPluginsShorthand && !hasPluginsArray) {
|
|
123
|
-
|
|
150
|
+
writeConfig(content);
|
|
124
151
|
console.log(`Updated: ${configPath} (added import)`);
|
|
125
152
|
console.log(` Note: 'plugins' is imported from another file.`);
|
|
126
153
|
console.log(` Add dynamicBlocksPlugin({ collections: ['pages'] }) to that file manually.`);
|
|
@@ -133,13 +160,13 @@ function modifyPayloadConfig(configPath, collectionsArg) {
|
|
|
133
160
|
result = injectPluginsBlock(content, collectionsArg);
|
|
134
161
|
}
|
|
135
162
|
if (result === null) {
|
|
136
|
-
|
|
163
|
+
writeConfig(content);
|
|
137
164
|
console.log(`Updated: ${configPath} (added import only)`);
|
|
138
165
|
console.log(` Could not auto-detect plugins array. Add manually:`);
|
|
139
166
|
console.log(` plugins: [ dynamicBlocksPlugin({ collections: [${collectionsArg}] }) ]`);
|
|
140
167
|
return;
|
|
141
168
|
}
|
|
142
|
-
|
|
169
|
+
writeConfig(result);
|
|
143
170
|
console.log(`Updated: ${configPath} (added dynamicBlocksPlugin)`);
|
|
144
171
|
}
|
|
145
172
|
function printNextSteps(dbAdapter) {
|
|
@@ -165,7 +192,14 @@ function printNextSteps(dbAdapter) {
|
|
|
165
192
|
function main() {
|
|
166
193
|
const args = process.argv.slice(2);
|
|
167
194
|
const collectionsFlag = args.find((a) => a.startsWith("--collections="));
|
|
168
|
-
const
|
|
195
|
+
const rawCollections = collectionsFlag ? collectionsFlag.replace("--collections=", "").split(",").map((s) => s.trim()) : ["pages"];
|
|
196
|
+
const invalidSlugs = rawCollections.filter((c) => !/^[a-z0-9_-]+$/i.test(c));
|
|
197
|
+
if (invalidSlugs.length > 0) {
|
|
198
|
+
console.error(`Error: Invalid collection slug(s): ${invalidSlugs.join(", ")}`);
|
|
199
|
+
console.error("Collection slugs may only contain letters, numbers, hyphens, and underscores.");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const collectionsValue = rawCollections;
|
|
169
203
|
const collectionsArg = collectionsValue.map((c) => `'${c}'`).join(", ");
|
|
170
204
|
const appDir = findAppDir();
|
|
171
205
|
if (!appDir) {
|
|
@@ -173,32 +207,52 @@ function main() {
|
|
|
173
207
|
process.exit(1);
|
|
174
208
|
}
|
|
175
209
|
const builderDir = path.join(appDir, "block-builder");
|
|
176
|
-
|
|
177
|
-
fs.
|
|
210
|
+
try {
|
|
211
|
+
if (!fs.existsSync(builderDir)) {
|
|
212
|
+
fs.mkdirSync(builderDir, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
} catch (err) {
|
|
215
|
+
console.error(`Error: Could not create directory ${builderDir}: ${err.message}`);
|
|
216
|
+
process.exit(1);
|
|
178
217
|
}
|
|
179
218
|
const pagePath = path.join(builderDir, "page.tsx");
|
|
180
219
|
const layoutPath = path.join(builderDir, "layout.tsx");
|
|
181
220
|
if (fs.existsSync(pagePath)) {
|
|
182
221
|
console.log(`Skipped: ${pagePath} already exists`);
|
|
183
222
|
} else {
|
|
184
|
-
|
|
185
|
-
|
|
223
|
+
try {
|
|
224
|
+
fs.writeFileSync(pagePath, PAGE_CONTENT);
|
|
225
|
+
console.log(`Created: ${pagePath}`);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error(`Error: Could not write ${pagePath}: ${err.message}`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
186
230
|
}
|
|
187
231
|
if (fs.existsSync(layoutPath)) {
|
|
188
232
|
console.log(`Skipped: ${layoutPath} already exists`);
|
|
189
233
|
} else {
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
try {
|
|
235
|
+
fs.writeFileSync(layoutPath, LAYOUT_CONTENT);
|
|
236
|
+
console.log(`Created: ${layoutPath}`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error(`Error: Could not write ${layoutPath}: ${err.message}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
192
241
|
}
|
|
193
242
|
const payloadRouteDir = path.join(appDir, "(payload)");
|
|
194
243
|
const customScssPath = path.join(payloadRouteDir, "custom.scss");
|
|
195
244
|
if (fs.existsSync(customScssPath)) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
245
|
+
try {
|
|
246
|
+
const existing = fs.readFileSync(customScssPath, "utf8");
|
|
247
|
+
if (!existing.includes("@nextbridgehq/payload-block-builder")) {
|
|
248
|
+
fs.appendFileSync(customScssPath, "\n" + CUSTOM_SCSS_IMPORTS);
|
|
249
|
+
console.log(`Updated: ${customScssPath} (added admin field styles)`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log(`Skipped: ${customScssPath} already has block-builder imports`);
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error(`Error: Could not update ${customScssPath}: ${err.message}`);
|
|
255
|
+
process.exit(1);
|
|
202
256
|
}
|
|
203
257
|
}
|
|
204
258
|
const configPath = findPayloadConfig();
|
package/dist/client.cjs
CHANGED
|
@@ -77,6 +77,56 @@ function MediaPicker({ label, required, value, onChange }) {
|
|
|
77
77
|
"x"
|
|
78
78
|
))) : /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from Media Library")), /* @__PURE__ */ import_react.default.createElement(ListDrawer, { onSelect: handleSelect }));
|
|
79
79
|
}
|
|
80
|
+
function RelationshipPicker({ label, required, collection, value, onChange }) {
|
|
81
|
+
const changeRef = (0, import_react.useRef)(onChange);
|
|
82
|
+
const closeRef = (0, import_react.useRef)(() => {
|
|
83
|
+
});
|
|
84
|
+
(0, import_react.useEffect)(() => {
|
|
85
|
+
changeRef.current = onChange;
|
|
86
|
+
});
|
|
87
|
+
const handleSelect = (0, import_react.useCallback)(
|
|
88
|
+
({ docID, doc }) => {
|
|
89
|
+
changeRef.current({
|
|
90
|
+
id: docID,
|
|
91
|
+
title: doc?.title ?? doc?.name ?? doc?.slug ?? null
|
|
92
|
+
});
|
|
93
|
+
closeRef.current();
|
|
94
|
+
},
|
|
95
|
+
[]
|
|
96
|
+
);
|
|
97
|
+
const [ListDrawer, ListDrawerToggler, { closeDrawer }] = (0, import_ui.useListDrawer)({
|
|
98
|
+
collectionSlugs: [collection]
|
|
99
|
+
});
|
|
100
|
+
closeRef.current = closeDrawer;
|
|
101
|
+
const relObj = value && typeof value === "object" ? value : null;
|
|
102
|
+
const relId = relObj?.id ?? (typeof value === "string" || typeof value === "number" ? value : null);
|
|
103
|
+
const relTitle = relObj?.title ? String(relObj.title) : null;
|
|
104
|
+
const [fetchedTitle, setFetchedTitle] = (0, import_react.useState)(null);
|
|
105
|
+
(0, import_react.useEffect)(() => {
|
|
106
|
+
if (!relId || relTitle) {
|
|
107
|
+
setFetchedTitle(null);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
fetch(`/api/${collection}/${String(relId)}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
|
|
111
|
+
if (doc) {
|
|
112
|
+
const t = doc.title ?? doc.name ?? doc.slug ?? null;
|
|
113
|
+
setFetchedTitle(t ? String(t) : null);
|
|
114
|
+
}
|
|
115
|
+
}).catch(() => {
|
|
116
|
+
});
|
|
117
|
+
}, [relId, relTitle, collection]);
|
|
118
|
+
const displayTitle = relTitle ?? fetchedTitle;
|
|
119
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", collection, ")")), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-area" }, relId ? /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-selected" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-upload-name" }, displayTitle ?? `ID: ${String(relId)}`), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-actions" }, /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Change"), /* @__PURE__ */ import_react.default.createElement(
|
|
120
|
+
"button",
|
|
121
|
+
{
|
|
122
|
+
type: "button",
|
|
123
|
+
className: "bdf-icon-btn bdf-icon-btn--danger",
|
|
124
|
+
title: "Remove",
|
|
125
|
+
onClick: () => onChange(null)
|
|
126
|
+
},
|
|
127
|
+
"\xD7"
|
|
128
|
+
))) : /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from ", collection)), /* @__PURE__ */ import_react.default.createElement(ListDrawer, { onSelect: handleSelect }));
|
|
129
|
+
}
|
|
80
130
|
function SchemaForm({ schema, value, onChange }) {
|
|
81
131
|
const set = (0, import_react.useCallback)(
|
|
82
132
|
(key, val) => onChange({ ...value, [key]: val }),
|
|
@@ -214,17 +264,19 @@ function FieldInput({ field, value, onChange }) {
|
|
|
214
264
|
onChange
|
|
215
265
|
}
|
|
216
266
|
);
|
|
217
|
-
case "relationship":
|
|
218
|
-
|
|
219
|
-
|
|
267
|
+
case "relationship": {
|
|
268
|
+
const collection = field.collection ?? "media";
|
|
269
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
270
|
+
RelationshipPicker,
|
|
220
271
|
{
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
272
|
+
label,
|
|
273
|
+
required: field.required,
|
|
274
|
+
collection,
|
|
275
|
+
value,
|
|
276
|
+
onChange
|
|
226
277
|
}
|
|
227
|
-
)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
228
280
|
case "json":
|
|
229
281
|
return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ import_react.default.createElement(
|
|
230
282
|
"textarea",
|
|
@@ -1172,6 +1224,7 @@ var useBuilderStore = (0, import_zustand.create)()(
|
|
|
1172
1224
|
|
|
1173
1225
|
// src/block-builder/components/canvas/TopBar.tsx
|
|
1174
1226
|
var import_react7 = __toESM(require("react"), 1);
|
|
1227
|
+
var import_lucide_react = require("lucide-react");
|
|
1175
1228
|
|
|
1176
1229
|
// src/block-builder/lib/mapToSaveRequest.ts
|
|
1177
1230
|
var TYPE_MAP = {
|
|
@@ -1360,7 +1413,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1360
1413
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
1361
1414
|
}, []);
|
|
1362
1415
|
async function handlePublish() {
|
|
1363
|
-
if (!activeBlock || isReadOnly) return;
|
|
1416
|
+
if (!activeBlock || isReadOnly) return false;
|
|
1364
1417
|
setNotification({ status: "publishing" });
|
|
1365
1418
|
try {
|
|
1366
1419
|
const req = mapToSaveRequest(activeBlock);
|
|
@@ -1377,13 +1430,15 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1377
1430
|
status: "success",
|
|
1378
1431
|
msg: `v${json.versionNumber ?? "?"} published successfully!`
|
|
1379
1432
|
});
|
|
1380
|
-
|
|
1433
|
+
onAfterPublish();
|
|
1434
|
+
return true;
|
|
1381
1435
|
} else {
|
|
1382
1436
|
setNotification({
|
|
1383
1437
|
status: "error",
|
|
1384
1438
|
title: "Failed to publish block",
|
|
1385
1439
|
errors: json.errors ?? ["An unknown error occurred."]
|
|
1386
1440
|
});
|
|
1441
|
+
return false;
|
|
1387
1442
|
}
|
|
1388
1443
|
} catch (err) {
|
|
1389
1444
|
setNotification({
|
|
@@ -1391,6 +1446,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1391
1446
|
title: "Network error",
|
|
1392
1447
|
errors: [err instanceof Error ? err.message : "Could not reach the server."]
|
|
1393
1448
|
});
|
|
1449
|
+
return false;
|
|
1394
1450
|
}
|
|
1395
1451
|
}
|
|
1396
1452
|
function handleExport() {
|
|
@@ -1418,9 +1474,9 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1418
1474
|
className: "bb-block-picker__trigger",
|
|
1419
1475
|
onClick: () => setBlockPickerOpen((o) => !o)
|
|
1420
1476
|
},
|
|
1421
|
-
/* @__PURE__ */ import_react7.default.createElement(
|
|
1477
|
+
/* @__PURE__ */ import_react7.default.createElement(import_lucide_react.Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
|
|
1422
1478
|
/* @__PURE__ */ import_react7.default.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
|
|
1423
|
-
/* @__PURE__ */ import_react7.default.createElement(
|
|
1479
|
+
/* @__PURE__ */ import_react7.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
|
|
1424
1480
|
), blockPickerOpen && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ import_react7.default.createElement(
|
|
1425
1481
|
"button",
|
|
1426
1482
|
{
|
|
@@ -1444,7 +1500,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1444
1500
|
/* @__PURE__ */ import_react7.default.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
|
|
1445
1501
|
/* @__PURE__ */ import_react7.default.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
|
|
1446
1502
|
selectedVersion?.isCurrent && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__badge" }, "current"),
|
|
1447
|
-
/* @__PURE__ */ import_react7.default.createElement(
|
|
1503
|
+
/* @__PURE__ */ import_react7.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
|
|
1448
1504
|
), versionDropdownOpen && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ import_react7.default.createElement(
|
|
1449
1505
|
"button",
|
|
1450
1506
|
{
|
|
@@ -1485,9 +1541,8 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1485
1541
|
{
|
|
1486
1542
|
type: "button",
|
|
1487
1543
|
onClick: async () => {
|
|
1488
|
-
await handlePublish();
|
|
1489
|
-
|
|
1490
|
-
onRestoreVersion();
|
|
1544
|
+
const success = await handlePublish();
|
|
1545
|
+
if (success) onRestoreVersion();
|
|
1491
1546
|
},
|
|
1492
1547
|
disabled: notification?.status === "publishing" || !activeBlock,
|
|
1493
1548
|
className: "bb-btn bb-btn--warning"
|
|
@@ -1502,11 +1557,12 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1502
1557
|
className: "bb-btn bb-btn--primary"
|
|
1503
1558
|
},
|
|
1504
1559
|
notification?.status === "publishing" ? "Publishing..." : "Publish to Payload"
|
|
1505
|
-
))), notification?.status === "publishing" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) },
|
|
1560
|
+
))), notification?.status === "publishing" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ import_react7.default.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ import_react7.default.createElement("li", { key: i }, e)))), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))));
|
|
1506
1561
|
}
|
|
1507
1562
|
|
|
1508
1563
|
// src/block-builder/components/canvas/BlockList.tsx
|
|
1509
1564
|
var import_react8 = __toESM(require("react"), 1);
|
|
1565
|
+
var import_lucide_react2 = require("lucide-react");
|
|
1510
1566
|
function BlockList() {
|
|
1511
1567
|
const blocks = useBuilderStore((s) => s.blocks);
|
|
1512
1568
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
@@ -1514,7 +1570,7 @@ function BlockList() {
|
|
|
1514
1570
|
const removeBlock = useBuilderStore((s) => s.removeBlock);
|
|
1515
1571
|
const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
|
|
1516
1572
|
const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
|
|
1517
|
-
return /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ import_react8.default.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" },
|
|
1573
|
+
return /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ import_react8.default.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ import_react8.default.createElement(import_lucide_react2.Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__body" }, blocks.length === 0 && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ import_react8.default.createElement("br", null), "Click + to create one."), blocks.map((block) => {
|
|
1518
1574
|
const isActive = block.id === activeBlockId;
|
|
1519
1575
|
return /* @__PURE__ */ import_react8.default.createElement(
|
|
1520
1576
|
"div",
|
|
@@ -1539,7 +1595,7 @@ function BlockList() {
|
|
|
1539
1595
|
className: "bb-block-action",
|
|
1540
1596
|
title: "Duplicate"
|
|
1541
1597
|
},
|
|
1542
|
-
|
|
1598
|
+
/* @__PURE__ */ import_react8.default.createElement(import_lucide_react2.Copy, { size: 12, strokeWidth: 1.75 })
|
|
1543
1599
|
),
|
|
1544
1600
|
/* @__PURE__ */ import_react8.default.createElement(
|
|
1545
1601
|
"button",
|
|
@@ -1549,7 +1605,7 @@ function BlockList() {
|
|
|
1549
1605
|
className: "bb-block-action bb-block-action--danger",
|
|
1550
1606
|
title: "Delete"
|
|
1551
1607
|
},
|
|
1552
|
-
|
|
1608
|
+
/* @__PURE__ */ import_react8.default.createElement(import_lucide_react2.Trash2, { size: 12, strokeWidth: 1.75 })
|
|
1553
1609
|
)
|
|
1554
1610
|
)
|
|
1555
1611
|
);
|
|
@@ -1566,24 +1622,19 @@ var import_modifiers = require("@dnd-kit/modifiers");
|
|
|
1566
1622
|
var import_react9 = __toESM(require("react"), 1);
|
|
1567
1623
|
var import_sortable = require("@dnd-kit/sortable");
|
|
1568
1624
|
var import_utilities = require("@dnd-kit/utilities");
|
|
1625
|
+
var import_lucide_react3 = require("lucide-react");
|
|
1569
1626
|
var ICON_MAP = {
|
|
1570
|
-
text:
|
|
1571
|
-
textarea:
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
upload:
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
point: "P",
|
|
1582
|
-
relationship: "->>",
|
|
1583
|
-
array: "[]",
|
|
1584
|
-
group: "{ }",
|
|
1585
|
-
json: "{ }",
|
|
1586
|
-
ui: "UI"
|
|
1627
|
+
text: import_lucide_react3.Type,
|
|
1628
|
+
textarea: import_lucide_react3.AlignLeft,
|
|
1629
|
+
number: import_lucide_react3.Hash,
|
|
1630
|
+
email: import_lucide_react3.Mail,
|
|
1631
|
+
date: import_lucide_react3.Calendar,
|
|
1632
|
+
checkbox: import_lucide_react3.CheckSquare,
|
|
1633
|
+
select: import_lucide_react3.ChevronDown,
|
|
1634
|
+
radio: import_lucide_react3.Circle,
|
|
1635
|
+
upload: import_lucide_react3.Upload,
|
|
1636
|
+
relationship: import_lucide_react3.Link,
|
|
1637
|
+
json: import_lucide_react3.Braces
|
|
1587
1638
|
};
|
|
1588
1639
|
function SortableFieldCard({ field, blockId, index }) {
|
|
1589
1640
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = (0, import_sortable.useSortable)({
|
|
@@ -1592,6 +1643,7 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1592
1643
|
const activeFieldId = useBuilderStore((s) => s.activeFieldId);
|
|
1593
1644
|
const setActiveField = useBuilderStore((s) => s.setActiveField);
|
|
1594
1645
|
const removeField = useBuilderStore((s) => s.removeField);
|
|
1646
|
+
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
1595
1647
|
const isActive = activeFieldId === field.id;
|
|
1596
1648
|
const wrapStyle = {
|
|
1597
1649
|
transform: import_utilities.CSS.Transform.toString(transform),
|
|
@@ -1612,10 +1664,10 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1612
1664
|
className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
|
|
1613
1665
|
onClick: () => setActiveField(isActive ? null : field.id)
|
|
1614
1666
|
},
|
|
1615
|
-
/* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__icon" },
|
|
1667
|
+
/* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ import_react9.default.createElement(FieldIcon, { type: field.type })),
|
|
1616
1668
|
/* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__required" }, "*"))),
|
|
1617
1669
|
/* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
|
|
1618
|
-
/* @__PURE__ */ import_react9.default.createElement(
|
|
1670
|
+
!isReadOnly && /* @__PURE__ */ import_react9.default.createElement(
|
|
1619
1671
|
"button",
|
|
1620
1672
|
{
|
|
1621
1673
|
type: "button",
|
|
@@ -1627,11 +1679,15 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1627
1679
|
className: "bb-field-card__delete",
|
|
1628
1680
|
title: "Remove field"
|
|
1629
1681
|
},
|
|
1630
|
-
|
|
1682
|
+
/* @__PURE__ */ import_react9.default.createElement(import_lucide_react3.X, { size: 12, strokeWidth: 2 })
|
|
1631
1683
|
)
|
|
1632
1684
|
)
|
|
1633
1685
|
);
|
|
1634
1686
|
}
|
|
1687
|
+
function FieldIcon({ type }) {
|
|
1688
|
+
const Icon = ICON_MAP[type];
|
|
1689
|
+
return Icon ? /* @__PURE__ */ import_react9.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
|
|
1690
|
+
}
|
|
1635
1691
|
|
|
1636
1692
|
// src/block-builder/components/canvas/BuilderCanvas.tsx
|
|
1637
1693
|
function BuilderCanvas() {
|
|
@@ -1748,21 +1804,15 @@ var import_react12 = __toESM(require("react"), 1);
|
|
|
1748
1804
|
var ALL_TYPES = [
|
|
1749
1805
|
"text",
|
|
1750
1806
|
"textarea",
|
|
1751
|
-
"richText",
|
|
1752
1807
|
"number",
|
|
1808
|
+
"email",
|
|
1809
|
+
"date",
|
|
1753
1810
|
"checkbox",
|
|
1754
1811
|
"select",
|
|
1755
1812
|
"radio",
|
|
1756
|
-
"date",
|
|
1757
1813
|
"upload",
|
|
1758
|
-
"email",
|
|
1759
|
-
"code",
|
|
1760
|
-
"point",
|
|
1761
1814
|
"relationship",
|
|
1762
|
-
"
|
|
1763
|
-
"group",
|
|
1764
|
-
"json",
|
|
1765
|
-
"ui"
|
|
1815
|
+
"json"
|
|
1766
1816
|
];
|
|
1767
1817
|
function FieldConfig() {
|
|
1768
1818
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
@@ -1934,7 +1984,7 @@ function ConfigPanel() {
|
|
|
1934
1984
|
|
|
1935
1985
|
// src/block-builder/components/sidebar/FieldPalette.tsx
|
|
1936
1986
|
var import_react14 = __toESM(require("react"), 1);
|
|
1937
|
-
var
|
|
1987
|
+
var import_lucide_react4 = require("lucide-react");
|
|
1938
1988
|
|
|
1939
1989
|
// src/block-builder/lib/field-palette.ts
|
|
1940
1990
|
var FIELD_PALETTE = [
|
|
@@ -1968,19 +2018,20 @@ function getFieldMeta(type) {
|
|
|
1968
2018
|
|
|
1969
2019
|
// src/block-builder/components/sidebar/FieldPalette.tsx
|
|
1970
2020
|
var ICON_MAP2 = {
|
|
1971
|
-
Type:
|
|
1972
|
-
AlignLeft:
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2021
|
+
Type: import_lucide_react4.Type,
|
|
2022
|
+
AlignLeft: import_lucide_react4.AlignLeft,
|
|
2023
|
+
AlignJustify: import_lucide_react4.AlignJustify,
|
|
2024
|
+
Hash: import_lucide_react4.Hash,
|
|
2025
|
+
Mail: import_lucide_react4.Mail,
|
|
2026
|
+
Calendar: import_lucide_react4.Calendar,
|
|
2027
|
+
CheckSquare: import_lucide_react4.CheckSquare,
|
|
2028
|
+
ChevronDown: import_lucide_react4.ChevronDown,
|
|
2029
|
+
Circle: import_lucide_react4.Circle,
|
|
2030
|
+
Upload: import_lucide_react4.Upload,
|
|
2031
|
+
Link: import_lucide_react4.Link,
|
|
2032
|
+
List: import_lucide_react4.List,
|
|
2033
|
+
Folder: import_lucide_react4.Folder,
|
|
2034
|
+
Braces: import_lucide_react4.Braces
|
|
1984
2035
|
};
|
|
1985
2036
|
function FieldPalette() {
|
|
1986
2037
|
const [search, setSearch] = (0, import_react14.useState)("");
|
|
@@ -2333,7 +2384,9 @@ function BuilderShell({ loadSlug }) {
|
|
|
2333
2384
|
setBlockDefs(
|
|
2334
2385
|
(json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
|
|
2335
2386
|
);
|
|
2336
|
-
}).catch(() => {
|
|
2387
|
+
}).catch((err) => {
|
|
2388
|
+
console.error("[block-builder] Failed to load block definitions:", err);
|
|
2389
|
+
setLoadError("Could not load block definitions. Please refresh the page.");
|
|
2337
2390
|
});
|
|
2338
2391
|
}, []);
|
|
2339
2392
|
const loadVersionsForSlug = (0, import_react16.useCallback)(async (slug) => {
|
package/dist/client.js
CHANGED
|
@@ -39,6 +39,56 @@ 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 RelationshipPicker({ label, required, collection, value, onChange }) {
|
|
43
|
+
const changeRef = useRef(onChange);
|
|
44
|
+
const closeRef = useRef(() => {
|
|
45
|
+
});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
changeRef.current = onChange;
|
|
48
|
+
});
|
|
49
|
+
const handleSelect = useCallback(
|
|
50
|
+
({ docID, doc }) => {
|
|
51
|
+
changeRef.current({
|
|
52
|
+
id: docID,
|
|
53
|
+
title: doc?.title ?? doc?.name ?? doc?.slug ?? null
|
|
54
|
+
});
|
|
55
|
+
closeRef.current();
|
|
56
|
+
},
|
|
57
|
+
[]
|
|
58
|
+
);
|
|
59
|
+
const [ListDrawer, ListDrawerToggler, { closeDrawer }] = useListDrawer({
|
|
60
|
+
collectionSlugs: [collection]
|
|
61
|
+
});
|
|
62
|
+
closeRef.current = closeDrawer;
|
|
63
|
+
const relObj = value && typeof value === "object" ? value : null;
|
|
64
|
+
const relId = relObj?.id ?? (typeof value === "string" || typeof value === "number" ? value : null);
|
|
65
|
+
const relTitle = relObj?.title ? String(relObj.title) : null;
|
|
66
|
+
const [fetchedTitle, setFetchedTitle] = useState(null);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!relId || relTitle) {
|
|
69
|
+
setFetchedTitle(null);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
fetch(`/api/${collection}/${String(relId)}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
|
|
73
|
+
if (doc) {
|
|
74
|
+
const t = doc.title ?? doc.name ?? doc.slug ?? null;
|
|
75
|
+
setFetchedTitle(t ? String(t) : null);
|
|
76
|
+
}
|
|
77
|
+
}).catch(() => {
|
|
78
|
+
});
|
|
79
|
+
}, [relId, relTitle, collection]);
|
|
80
|
+
const displayTitle = relTitle ?? fetchedTitle;
|
|
81
|
+
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 } }, "(", collection, ")")), /* @__PURE__ */ React.createElement("div", { className: "bdf-upload-area" }, relId ? /* @__PURE__ */ React.createElement("div", { className: "bdf-upload-selected" }, /* @__PURE__ */ React.createElement("span", { className: "bdf-upload-name" }, displayTitle ?? `ID: ${String(relId)}`), /* @__PURE__ */ React.createElement("div", { className: "bdf-upload-actions" }, /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Change"), /* @__PURE__ */ React.createElement(
|
|
82
|
+
"button",
|
|
83
|
+
{
|
|
84
|
+
type: "button",
|
|
85
|
+
className: "bdf-icon-btn bdf-icon-btn--danger",
|
|
86
|
+
title: "Remove",
|
|
87
|
+
onClick: () => onChange(null)
|
|
88
|
+
},
|
|
89
|
+
"\xD7"
|
|
90
|
+
))) : /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from ", collection)), /* @__PURE__ */ React.createElement(ListDrawer, { onSelect: handleSelect }));
|
|
91
|
+
}
|
|
42
92
|
function SchemaForm({ schema, value, onChange }) {
|
|
43
93
|
const set = useCallback(
|
|
44
94
|
(key, val) => onChange({ ...value, [key]: val }),
|
|
@@ -176,17 +226,19 @@ function FieldInput({ field, value, onChange }) {
|
|
|
176
226
|
onChange
|
|
177
227
|
}
|
|
178
228
|
);
|
|
179
|
-
case "relationship":
|
|
180
|
-
|
|
181
|
-
|
|
229
|
+
case "relationship": {
|
|
230
|
+
const collection = field.collection ?? "media";
|
|
231
|
+
return /* @__PURE__ */ React.createElement(
|
|
232
|
+
RelationshipPicker,
|
|
182
233
|
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
234
|
+
label,
|
|
235
|
+
required: field.required,
|
|
236
|
+
collection,
|
|
237
|
+
value,
|
|
238
|
+
onChange
|
|
188
239
|
}
|
|
189
|
-
)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
190
242
|
case "json":
|
|
191
243
|
return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, field.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(
|
|
192
244
|
"textarea",
|
|
@@ -1134,6 +1186,7 @@ var useBuilderStore = create()(
|
|
|
1134
1186
|
|
|
1135
1187
|
// src/block-builder/components/canvas/TopBar.tsx
|
|
1136
1188
|
import React7, { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
|
|
1189
|
+
import { Blocks, ChevronDown, X } from "lucide-react";
|
|
1137
1190
|
|
|
1138
1191
|
// src/block-builder/lib/mapToSaveRequest.ts
|
|
1139
1192
|
var TYPE_MAP = {
|
|
@@ -1322,7 +1375,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1322
1375
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
1323
1376
|
}, []);
|
|
1324
1377
|
async function handlePublish() {
|
|
1325
|
-
if (!activeBlock || isReadOnly) return;
|
|
1378
|
+
if (!activeBlock || isReadOnly) return false;
|
|
1326
1379
|
setNotification({ status: "publishing" });
|
|
1327
1380
|
try {
|
|
1328
1381
|
const req = mapToSaveRequest(activeBlock);
|
|
@@ -1339,13 +1392,15 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1339
1392
|
status: "success",
|
|
1340
1393
|
msg: `v${json.versionNumber ?? "?"} published successfully!`
|
|
1341
1394
|
});
|
|
1342
|
-
|
|
1395
|
+
onAfterPublish();
|
|
1396
|
+
return true;
|
|
1343
1397
|
} else {
|
|
1344
1398
|
setNotification({
|
|
1345
1399
|
status: "error",
|
|
1346
1400
|
title: "Failed to publish block",
|
|
1347
1401
|
errors: json.errors ?? ["An unknown error occurred."]
|
|
1348
1402
|
});
|
|
1403
|
+
return false;
|
|
1349
1404
|
}
|
|
1350
1405
|
} catch (err) {
|
|
1351
1406
|
setNotification({
|
|
@@ -1353,6 +1408,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1353
1408
|
title: "Network error",
|
|
1354
1409
|
errors: [err instanceof Error ? err.message : "Could not reach the server."]
|
|
1355
1410
|
});
|
|
1411
|
+
return false;
|
|
1356
1412
|
}
|
|
1357
1413
|
}
|
|
1358
1414
|
function handleExport() {
|
|
@@ -1380,9 +1436,9 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1380
1436
|
className: "bb-block-picker__trigger",
|
|
1381
1437
|
onClick: () => setBlockPickerOpen((o) => !o)
|
|
1382
1438
|
},
|
|
1383
|
-
/* @__PURE__ */ React7.createElement(
|
|
1439
|
+
/* @__PURE__ */ React7.createElement(Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
|
|
1384
1440
|
/* @__PURE__ */ React7.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
|
|
1385
|
-
/* @__PURE__ */ React7.createElement(
|
|
1441
|
+
/* @__PURE__ */ React7.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
|
|
1386
1442
|
), 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
1443
|
"button",
|
|
1388
1444
|
{
|
|
@@ -1406,7 +1462,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1406
1462
|
/* @__PURE__ */ React7.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
|
|
1407
1463
|
/* @__PURE__ */ React7.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
|
|
1408
1464
|
selectedVersion?.isCurrent && /* @__PURE__ */ React7.createElement("span", { className: "bb-version-selector__badge" }, "current"),
|
|
1409
|
-
/* @__PURE__ */ React7.createElement(
|
|
1465
|
+
/* @__PURE__ */ React7.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
|
|
1410
1466
|
), 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
1467
|
"button",
|
|
1412
1468
|
{
|
|
@@ -1447,9 +1503,8 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1447
1503
|
{
|
|
1448
1504
|
type: "button",
|
|
1449
1505
|
onClick: async () => {
|
|
1450
|
-
await handlePublish();
|
|
1451
|
-
|
|
1452
|
-
onRestoreVersion();
|
|
1506
|
+
const success = await handlePublish();
|
|
1507
|
+
if (success) onRestoreVersion();
|
|
1453
1508
|
},
|
|
1454
1509
|
disabled: notification?.status === "publishing" || !activeBlock,
|
|
1455
1510
|
className: "bb-btn bb-btn--warning"
|
|
@@ -1464,11 +1519,12 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
|
|
|
1464
1519
|
className: "bb-btn bb-btn--primary"
|
|
1465
1520
|
},
|
|
1466
1521
|
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) },
|
|
1522
|
+
))), 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
1523
|
}
|
|
1469
1524
|
|
|
1470
1525
|
// src/block-builder/components/canvas/BlockList.tsx
|
|
1471
1526
|
import React8 from "react";
|
|
1527
|
+
import { Copy, Trash2, Plus } from "lucide-react";
|
|
1472
1528
|
function BlockList() {
|
|
1473
1529
|
const blocks = useBuilderStore((s) => s.blocks);
|
|
1474
1530
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
@@ -1476,7 +1532,7 @@ function BlockList() {
|
|
|
1476
1532
|
const removeBlock = useBuilderStore((s) => s.removeBlock);
|
|
1477
1533
|
const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
|
|
1478
1534
|
const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
|
|
1479
|
-
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" },
|
|
1535
|
+
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" }, blocks.length === 0 && /* @__PURE__ */ React8.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ React8.createElement("br", null), "Click + to create one."), blocks.map((block) => {
|
|
1480
1536
|
const isActive = block.id === activeBlockId;
|
|
1481
1537
|
return /* @__PURE__ */ React8.createElement(
|
|
1482
1538
|
"div",
|
|
@@ -1501,7 +1557,7 @@ function BlockList() {
|
|
|
1501
1557
|
className: "bb-block-action",
|
|
1502
1558
|
title: "Duplicate"
|
|
1503
1559
|
},
|
|
1504
|
-
|
|
1560
|
+
/* @__PURE__ */ React8.createElement(Copy, { size: 12, strokeWidth: 1.75 })
|
|
1505
1561
|
),
|
|
1506
1562
|
/* @__PURE__ */ React8.createElement(
|
|
1507
1563
|
"button",
|
|
@@ -1511,7 +1567,7 @@ function BlockList() {
|
|
|
1511
1567
|
className: "bb-block-action bb-block-action--danger",
|
|
1512
1568
|
title: "Delete"
|
|
1513
1569
|
},
|
|
1514
|
-
|
|
1570
|
+
/* @__PURE__ */ React8.createElement(Trash2, { size: 12, strokeWidth: 1.75 })
|
|
1515
1571
|
)
|
|
1516
1572
|
)
|
|
1517
1573
|
);
|
|
@@ -1539,24 +1595,32 @@ import { restrictToVerticalAxis, restrictToParentElement } from "@dnd-kit/modifi
|
|
|
1539
1595
|
import React9 from "react";
|
|
1540
1596
|
import { useSortable } from "@dnd-kit/sortable";
|
|
1541
1597
|
import { CSS } from "@dnd-kit/utilities";
|
|
1598
|
+
import {
|
|
1599
|
+
Type,
|
|
1600
|
+
AlignLeft,
|
|
1601
|
+
Hash,
|
|
1602
|
+
Mail,
|
|
1603
|
+
Calendar,
|
|
1604
|
+
CheckSquare,
|
|
1605
|
+
ChevronDown as ChevronDown2,
|
|
1606
|
+
Circle,
|
|
1607
|
+
Upload,
|
|
1608
|
+
Link,
|
|
1609
|
+
Braces,
|
|
1610
|
+
X as X2
|
|
1611
|
+
} from "lucide-react";
|
|
1542
1612
|
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"
|
|
1613
|
+
text: Type,
|
|
1614
|
+
textarea: AlignLeft,
|
|
1615
|
+
number: Hash,
|
|
1616
|
+
email: Mail,
|
|
1617
|
+
date: Calendar,
|
|
1618
|
+
checkbox: CheckSquare,
|
|
1619
|
+
select: ChevronDown2,
|
|
1620
|
+
radio: Circle,
|
|
1621
|
+
upload: Upload,
|
|
1622
|
+
relationship: Link,
|
|
1623
|
+
json: Braces
|
|
1560
1624
|
};
|
|
1561
1625
|
function SortableFieldCard({ field, blockId, index }) {
|
|
1562
1626
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
@@ -1565,6 +1629,7 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1565
1629
|
const activeFieldId = useBuilderStore((s) => s.activeFieldId);
|
|
1566
1630
|
const setActiveField = useBuilderStore((s) => s.setActiveField);
|
|
1567
1631
|
const removeField = useBuilderStore((s) => s.removeField);
|
|
1632
|
+
const isReadOnly = useBuilderStore((s) => s.isReadOnly);
|
|
1568
1633
|
const isActive = activeFieldId === field.id;
|
|
1569
1634
|
const wrapStyle = {
|
|
1570
1635
|
transform: CSS.Transform.toString(transform),
|
|
@@ -1585,10 +1650,10 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1585
1650
|
className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
|
|
1586
1651
|
onClick: () => setActiveField(isActive ? null : field.id)
|
|
1587
1652
|
},
|
|
1588
|
-
/* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__icon" },
|
|
1653
|
+
/* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ React9.createElement(FieldIcon, { type: field.type })),
|
|
1589
1654
|
/* @__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
1655
|
/* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
|
|
1591
|
-
/* @__PURE__ */ React9.createElement(
|
|
1656
|
+
!isReadOnly && /* @__PURE__ */ React9.createElement(
|
|
1592
1657
|
"button",
|
|
1593
1658
|
{
|
|
1594
1659
|
type: "button",
|
|
@@ -1600,11 +1665,15 @@ function SortableFieldCard({ field, blockId, index }) {
|
|
|
1600
1665
|
className: "bb-field-card__delete",
|
|
1601
1666
|
title: "Remove field"
|
|
1602
1667
|
},
|
|
1603
|
-
|
|
1668
|
+
/* @__PURE__ */ React9.createElement(X2, { size: 12, strokeWidth: 2 })
|
|
1604
1669
|
)
|
|
1605
1670
|
)
|
|
1606
1671
|
);
|
|
1607
1672
|
}
|
|
1673
|
+
function FieldIcon({ type }) {
|
|
1674
|
+
const Icon = ICON_MAP[type];
|
|
1675
|
+
return Icon ? /* @__PURE__ */ React9.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
|
|
1676
|
+
}
|
|
1608
1677
|
|
|
1609
1678
|
// src/block-builder/components/canvas/BuilderCanvas.tsx
|
|
1610
1679
|
function BuilderCanvas() {
|
|
@@ -1721,21 +1790,15 @@ import React12 from "react";
|
|
|
1721
1790
|
var ALL_TYPES = [
|
|
1722
1791
|
"text",
|
|
1723
1792
|
"textarea",
|
|
1724
|
-
"richText",
|
|
1725
1793
|
"number",
|
|
1794
|
+
"email",
|
|
1795
|
+
"date",
|
|
1726
1796
|
"checkbox",
|
|
1727
1797
|
"select",
|
|
1728
1798
|
"radio",
|
|
1729
|
-
"date",
|
|
1730
1799
|
"upload",
|
|
1731
|
-
"email",
|
|
1732
|
-
"code",
|
|
1733
|
-
"point",
|
|
1734
1800
|
"relationship",
|
|
1735
|
-
"
|
|
1736
|
-
"group",
|
|
1737
|
-
"json",
|
|
1738
|
-
"ui"
|
|
1801
|
+
"json"
|
|
1739
1802
|
];
|
|
1740
1803
|
function FieldConfig() {
|
|
1741
1804
|
const activeBlockId = useBuilderStore((s) => s.activeBlockId);
|
|
@@ -1908,19 +1971,20 @@ function ConfigPanel() {
|
|
|
1908
1971
|
// src/block-builder/components/sidebar/FieldPalette.tsx
|
|
1909
1972
|
import React14, { useState as useState6 } from "react";
|
|
1910
1973
|
import {
|
|
1911
|
-
Type,
|
|
1912
|
-
AlignLeft,
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1974
|
+
Type as Type2,
|
|
1975
|
+
AlignLeft as AlignLeft2,
|
|
1976
|
+
AlignJustify,
|
|
1977
|
+
Hash as Hash2,
|
|
1978
|
+
Mail as Mail2,
|
|
1979
|
+
Calendar as Calendar2,
|
|
1980
|
+
CheckSquare as CheckSquare2,
|
|
1981
|
+
ChevronDown as ChevronDown3,
|
|
1982
|
+
Circle as Circle2,
|
|
1983
|
+
Upload as Upload2,
|
|
1984
|
+
Link as Link2,
|
|
1921
1985
|
List,
|
|
1922
1986
|
Folder,
|
|
1923
|
-
Braces
|
|
1987
|
+
Braces as Braces2
|
|
1924
1988
|
} from "lucide-react";
|
|
1925
1989
|
|
|
1926
1990
|
// src/block-builder/lib/field-palette.ts
|
|
@@ -1955,19 +2019,20 @@ function getFieldMeta(type) {
|
|
|
1955
2019
|
|
|
1956
2020
|
// src/block-builder/components/sidebar/FieldPalette.tsx
|
|
1957
2021
|
var ICON_MAP2 = {
|
|
1958
|
-
Type,
|
|
1959
|
-
AlignLeft,
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2022
|
+
Type: Type2,
|
|
2023
|
+
AlignLeft: AlignLeft2,
|
|
2024
|
+
AlignJustify,
|
|
2025
|
+
Hash: Hash2,
|
|
2026
|
+
Mail: Mail2,
|
|
2027
|
+
Calendar: Calendar2,
|
|
2028
|
+
CheckSquare: CheckSquare2,
|
|
2029
|
+
ChevronDown: ChevronDown3,
|
|
2030
|
+
Circle: Circle2,
|
|
2031
|
+
Upload: Upload2,
|
|
2032
|
+
Link: Link2,
|
|
1968
2033
|
List,
|
|
1969
2034
|
Folder,
|
|
1970
|
-
Braces
|
|
2035
|
+
Braces: Braces2
|
|
1971
2036
|
};
|
|
1972
2037
|
function FieldPalette() {
|
|
1973
2038
|
const [search, setSearch] = useState6("");
|
|
@@ -2320,7 +2385,9 @@ function BuilderShell({ loadSlug }) {
|
|
|
2320
2385
|
setBlockDefs(
|
|
2321
2386
|
(json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
|
|
2322
2387
|
);
|
|
2323
|
-
}).catch(() => {
|
|
2388
|
+
}).catch((err) => {
|
|
2389
|
+
console.error("[block-builder] Failed to load block definitions:", err);
|
|
2390
|
+
setLoadError("Could not load block definitions. Please refresh the page.");
|
|
2324
2391
|
});
|
|
2325
2392
|
}, []);
|
|
2326
2393
|
const loadVersionsForSlug = useCallback5(async (slug) => {
|
|
@@ -2437,10 +2504,10 @@ function BuilderShell({ loadSlug }) {
|
|
|
2437
2504
|
|
|
2438
2505
|
// src/components/BlockBuilderNavLink/index.tsx
|
|
2439
2506
|
import React17 from "react";
|
|
2440
|
-
import
|
|
2507
|
+
import Link3 from "next/link";
|
|
2441
2508
|
function BlockBuilderNavLink() {
|
|
2442
2509
|
return /* @__PURE__ */ React17.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ React17.createElement(
|
|
2443
|
-
|
|
2510
|
+
Link3,
|
|
2444
2511
|
{
|
|
2445
2512
|
href: "/block-builder",
|
|
2446
2513
|
target: "_blank",
|
package/dist/index.cjs
CHANGED
|
@@ -157,18 +157,23 @@ function dbLayoutField(fieldName = "dbLayout", tabLabel = "DB Layout") {
|
|
|
157
157
|
},
|
|
158
158
|
fields: [
|
|
159
159
|
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
type: "row",
|
|
161
|
+
fields: [
|
|
162
|
+
{
|
|
163
|
+
name: "blockDefinition",
|
|
164
|
+
type: "relationship",
|
|
165
|
+
relationTo: "block-definitions",
|
|
166
|
+
required: true,
|
|
167
|
+
admin: { description: "Which block type to use.", width: "50%" }
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "blockVersion",
|
|
171
|
+
type: "relationship",
|
|
172
|
+
relationTo: "block-definition-versions",
|
|
173
|
+
required: true,
|
|
174
|
+
admin: { description: "Which schema version to use.", width: "50%" }
|
|
175
|
+
}
|
|
176
|
+
]
|
|
172
177
|
},
|
|
173
178
|
{
|
|
174
179
|
name: "instanceId",
|
|
@@ -360,7 +365,15 @@ var generateEndpoint = async (req) => {
|
|
|
360
365
|
var import_uuid = require("uuid");
|
|
361
366
|
var REVERSE_TYPE_MAP = {
|
|
362
367
|
richtext: "richText",
|
|
363
|
-
image: "upload"
|
|
368
|
+
image: "upload",
|
|
369
|
+
file: "upload",
|
|
370
|
+
// file and image both map to upload in the builder
|
|
371
|
+
multiselect: "select",
|
|
372
|
+
// builder has no multiselect — nearest equivalent
|
|
373
|
+
url: "text",
|
|
374
|
+
// builder has no url field — falls back to text
|
|
375
|
+
color: "text"
|
|
376
|
+
// builder has no color field — falls back to text
|
|
364
377
|
};
|
|
365
378
|
var VALID_BUILDER_TYPES = /* @__PURE__ */ new Set([
|
|
366
379
|
"text",
|
|
@@ -387,7 +400,11 @@ var VALID_BUILDER_TYPES = /* @__PURE__ */ new Set([
|
|
|
387
400
|
function fieldToBuilderField(raw) {
|
|
388
401
|
const rawType = String(raw.type ?? "text");
|
|
389
402
|
const mappedType = REVERSE_TYPE_MAP[rawType] ?? rawType;
|
|
390
|
-
const
|
|
403
|
+
const isKnown = VALID_BUILDER_TYPES.has(mappedType);
|
|
404
|
+
if (!isKnown) {
|
|
405
|
+
console.warn(`[block-builder] Unknown field type "${rawType}" \u2014 rendering as "text". Add a mapping in REVERSE_TYPE_MAP.`);
|
|
406
|
+
}
|
|
407
|
+
const fieldType = isKnown ? mappedType : "text";
|
|
391
408
|
const field = {
|
|
392
409
|
id: (0, import_uuid.v4)(),
|
|
393
410
|
type: fieldType,
|
|
@@ -424,7 +441,7 @@ function fieldToBuilderField(raw) {
|
|
|
424
441
|
function slugToInterfaceName(slug) {
|
|
425
442
|
return slug.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
426
443
|
}
|
|
427
|
-
function schemaToBuilderBlock(slug,
|
|
444
|
+
function schemaToBuilderBlock(slug, _name, labels, schemaFields) {
|
|
428
445
|
return {
|
|
429
446
|
id: (0, import_uuid.v4)(),
|
|
430
447
|
slug,
|
package/dist/index.js
CHANGED
|
@@ -129,18 +129,23 @@ function dbLayoutField(fieldName = "dbLayout", tabLabel = "DB Layout") {
|
|
|
129
129
|
},
|
|
130
130
|
fields: [
|
|
131
131
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
type: "row",
|
|
133
|
+
fields: [
|
|
134
|
+
{
|
|
135
|
+
name: "blockDefinition",
|
|
136
|
+
type: "relationship",
|
|
137
|
+
relationTo: "block-definitions",
|
|
138
|
+
required: true,
|
|
139
|
+
admin: { description: "Which block type to use.", width: "50%" }
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "blockVersion",
|
|
143
|
+
type: "relationship",
|
|
144
|
+
relationTo: "block-definition-versions",
|
|
145
|
+
required: true,
|
|
146
|
+
admin: { description: "Which schema version to use.", width: "50%" }
|
|
147
|
+
}
|
|
148
|
+
]
|
|
144
149
|
},
|
|
145
150
|
{
|
|
146
151
|
name: "instanceId",
|
|
@@ -332,7 +337,15 @@ var generateEndpoint = async (req) => {
|
|
|
332
337
|
import { v4 as uuidv4 } from "uuid";
|
|
333
338
|
var REVERSE_TYPE_MAP = {
|
|
334
339
|
richtext: "richText",
|
|
335
|
-
image: "upload"
|
|
340
|
+
image: "upload",
|
|
341
|
+
file: "upload",
|
|
342
|
+
// file and image both map to upload in the builder
|
|
343
|
+
multiselect: "select",
|
|
344
|
+
// builder has no multiselect — nearest equivalent
|
|
345
|
+
url: "text",
|
|
346
|
+
// builder has no url field — falls back to text
|
|
347
|
+
color: "text"
|
|
348
|
+
// builder has no color field — falls back to text
|
|
336
349
|
};
|
|
337
350
|
var VALID_BUILDER_TYPES = /* @__PURE__ */ new Set([
|
|
338
351
|
"text",
|
|
@@ -359,7 +372,11 @@ var VALID_BUILDER_TYPES = /* @__PURE__ */ new Set([
|
|
|
359
372
|
function fieldToBuilderField(raw) {
|
|
360
373
|
const rawType = String(raw.type ?? "text");
|
|
361
374
|
const mappedType = REVERSE_TYPE_MAP[rawType] ?? rawType;
|
|
362
|
-
const
|
|
375
|
+
const isKnown = VALID_BUILDER_TYPES.has(mappedType);
|
|
376
|
+
if (!isKnown) {
|
|
377
|
+
console.warn(`[block-builder] Unknown field type "${rawType}" \u2014 rendering as "text". Add a mapping in REVERSE_TYPE_MAP.`);
|
|
378
|
+
}
|
|
379
|
+
const fieldType = isKnown ? mappedType : "text";
|
|
363
380
|
const field = {
|
|
364
381
|
id: uuidv4(),
|
|
365
382
|
type: fieldType,
|
|
@@ -396,7 +413,7 @@ function fieldToBuilderField(raw) {
|
|
|
396
413
|
function slugToInterfaceName(slug) {
|
|
397
414
|
return slug.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
398
415
|
}
|
|
399
|
-
function schemaToBuilderBlock(slug,
|
|
416
|
+
function schemaToBuilderBlock(slug, _name, labels, schemaFields) {
|
|
400
417
|
return {
|
|
401
418
|
id: uuidv4(),
|
|
402
419
|
slug,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextbridgehq/payload-block-builder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Block Builder for Payload CMS",
|
|
5
|
-
"keywords": ["payload", "payload-plugin", "cms", "block-builder", "dynamic-blocks"],
|
|
5
|
+
"keywords": ["payload", "payloadcms", "payload-plugin", "cms", "block-builder", "dynamic-blocks"],
|
|
6
6
|
"homepage": "https://github.com/nextbridgehq/block-builder",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|