@hanzo/ui 8.0.24 → 8.0.25
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 +59 -0
- package/dist/chunk-3NG33DV2.cjs +607 -0
- package/dist/chunk-3NG33DV2.cjs.map +1 -0
- package/dist/chunk-5CZOYONP.js +343 -0
- package/dist/chunk-5CZOYONP.js.map +1 -0
- package/dist/{chunk-P2P6ST6J.js → chunk-7ZNK6JSS.js} +9 -394
- package/dist/chunk-7ZNK6JSS.js.map +1 -0
- package/dist/chunk-GVCOAJLO.js +397 -0
- package/dist/chunk-GVCOAJLO.js.map +1 -0
- package/dist/chunk-GWNNLYX4.cjs +353 -0
- package/dist/chunk-GWNNLYX4.cjs.map +1 -0
- package/dist/{chunk-NBZDIMP3.cjs → chunk-ONVERVFG.cjs} +33 -438
- package/dist/chunk-ONVERVFG.cjs.map +1 -0
- package/dist/chunk-P2QU5MN5.js +555 -0
- package/dist/chunk-P2QU5MN5.js.map +1 -0
- package/dist/chunk-WHJR5S4L.cjs +418 -0
- package/dist/chunk-WHJR5S4L.cjs.map +1 -0
- package/dist/framework/CollectionBuilder.d.ts +12 -0
- package/dist/framework/CollectionsBrowser.d.ts +26 -19
- package/dist/framework/DocTypeRecords.d.ts +31 -11
- package/dist/framework/MediaGrid.d.ts +42 -0
- package/dist/framework/RecordCards.d.ts +35 -0
- package/dist/framework/builder-logic.d.ts +76 -0
- package/dist/framework/client.d.ts +31 -44
- package/dist/framework/core.cjs +213 -0
- package/dist/framework/core.cjs.map +1 -0
- package/dist/framework/core.d.ts +6 -0
- package/dist/framework/core.js +4 -0
- package/dist/framework/core.js.map +1 -0
- package/dist/framework/data.d.ts +2 -2
- package/dist/framework/fields.d.ts +47 -4
- package/dist/framework/index.cjs +1340 -0
- package/dist/framework/index.cjs.map +1 -0
- package/dist/framework/index.d.ts +9 -23
- package/dist/framework/index.js +1119 -0
- package/dist/framework/index.js.map +1 -0
- package/dist/framework/media.d.ts +53 -0
- package/dist/framework/parts.d.ts +74 -0
- package/dist/framework/responsive.d.ts +41 -0
- package/dist/framework/types.d.ts +13 -7
- package/dist/product/SelectMenu.d.ts +13 -1
- package/dist/product/index.cjs +2061 -336
- package/dist/product/index.cjs.map +1 -1
- package/dist/product/index.js +1909 -3
- package/dist/product/index.js.map +1 -1
- package/dist/product/social/index.cjs +17 -16
- package/dist/product/social/index.js +3 -2
- package/package.json +11 -5
- package/dist/chunk-2BM42AZR.cjs +0 -2301
- package/dist/chunk-2BM42AZR.cjs.map +0 -1
- package/dist/chunk-7P7R7FC5.js +0 -2231
- package/dist/chunk-7P7R7FC5.js.map +0 -1
- package/dist/chunk-NBZDIMP3.cjs.map +0 -1
- package/dist/chunk-P2P6ST6J.js.map +0 -1
- package/dist/framework/Loader.d.ts +0 -4
- package/dist/framework.cjs +0 -760
- package/dist/framework.cjs.map +0 -1
- package/dist/framework.d.ts +0 -1
- package/dist/framework.js +0 -730
- package/dist/framework.js.map +0 -1
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// src/framework/types.ts
|
|
3
|
+
var DRAFT = 0;
|
|
4
|
+
var SUBMITTED = 1;
|
|
5
|
+
var CANCELLED = 2;
|
|
6
|
+
var REDACTED_MARKER = "__set__";
|
|
7
|
+
|
|
8
|
+
// src/framework/client.ts
|
|
9
|
+
var enc = encodeURIComponent;
|
|
10
|
+
var asRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
11
|
+
function rows(payload) {
|
|
12
|
+
if (Array.isArray(payload)) {
|
|
13
|
+
return payload.filter((x) => x && typeof x === "object");
|
|
14
|
+
}
|
|
15
|
+
const o = asRecord(payload);
|
|
16
|
+
for (const k of ["data", "items", "rows"]) {
|
|
17
|
+
if (Array.isArray(o[k])) {
|
|
18
|
+
return o[k].filter((x) => x && typeof x === "object");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
var asDoc = (v) => asRecord(v);
|
|
24
|
+
var asDocType = (v) => {
|
|
25
|
+
const r = asRecord(v);
|
|
26
|
+
return {
|
|
27
|
+
...r,
|
|
28
|
+
name: String(r.name ?? ""),
|
|
29
|
+
fields: Array.isArray(r.fields) ? r.fields : []
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
function listQuery(q) {
|
|
33
|
+
if (!q) return "";
|
|
34
|
+
const p = new URLSearchParams();
|
|
35
|
+
if (q.filters && Object.keys(q.filters).length) p.set("filters", JSON.stringify(q.filters));
|
|
36
|
+
if (q.fields && q.fields.length) p.set("fields", q.fields.join(","));
|
|
37
|
+
if (q.orderBy) p.set("order_by", q.orderBy);
|
|
38
|
+
if (q.limit) p.set("limit", String(q.limit));
|
|
39
|
+
const s = p.toString();
|
|
40
|
+
return s ? `?${s}` : "";
|
|
41
|
+
}
|
|
42
|
+
function createFrameworkClient(t) {
|
|
43
|
+
return {
|
|
44
|
+
doctypes: {
|
|
45
|
+
list: () => t.get("doctypes").then((r) => rows(r).map(asDocType)),
|
|
46
|
+
get: (name) => t.get(`doctypes/${enc(name)}`).then(asDocType),
|
|
47
|
+
create: (dt) => t.post("doctypes", dt).then(asDocType),
|
|
48
|
+
update: (name, dt) => t.put(`doctypes/${enc(name)}`, dt).then(asDocType),
|
|
49
|
+
remove: (name) => t.del(`doctypes/${enc(name)}`)
|
|
50
|
+
},
|
|
51
|
+
records: {
|
|
52
|
+
list: (doctype, q) => t.get(`${enc(doctype)}${listQuery(q)}`).then((r) => rows(r).map(asDoc)),
|
|
53
|
+
get: (doctype, name) => t.get(`${enc(doctype)}/${enc(name)}`).then(asDoc),
|
|
54
|
+
create: (doctype, data) => t.post(enc(doctype), data).then(asDoc),
|
|
55
|
+
update: (doctype, name, data) => t.put(`${enc(doctype)}/${enc(name)}`, data).then(asDoc),
|
|
56
|
+
remove: (doctype, name) => t.del(`${enc(doctype)}/${enc(name)}`),
|
|
57
|
+
submit: (doctype, name) => t.post(`${enc(doctype)}/${enc(name)}/submit`).then(asDoc),
|
|
58
|
+
cancel: (doctype, name) => t.post(`${enc(doctype)}/${enc(name)}/cancel`).then(asDoc)
|
|
59
|
+
},
|
|
60
|
+
modules: {
|
|
61
|
+
list: () => t.get("modules").then(
|
|
62
|
+
(r) => rows(r).map((m) => ({
|
|
63
|
+
module: String(m.module ?? ""),
|
|
64
|
+
doctypes: Array.isArray(m.doctypes) ? m.doctypes : []
|
|
65
|
+
}))
|
|
66
|
+
),
|
|
67
|
+
get: (module) => t.get(`modules/${enc(module)}`).then((v) => {
|
|
68
|
+
const r = asRecord(v);
|
|
69
|
+
return {
|
|
70
|
+
module: String(r.module ?? module),
|
|
71
|
+
doctypes: Array.isArray(r.doctypes) ? r.doctypes : [],
|
|
72
|
+
installed: Array.isArray(r.installed) ? r.installed : []
|
|
73
|
+
};
|
|
74
|
+
}),
|
|
75
|
+
install: (module) => t.post(`modules/${enc(module)}/install`).then((v) => {
|
|
76
|
+
const r = asRecord(v);
|
|
77
|
+
return {
|
|
78
|
+
module: String(r.module ?? module),
|
|
79
|
+
created: Array.isArray(r.created) ? r.created : [],
|
|
80
|
+
existing: Array.isArray(r.existing) ? r.existing : []
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
roles: {
|
|
85
|
+
list: () => t.get("roles").then(
|
|
86
|
+
(r) => rows(r).map((x) => ({ user: String(x.user ?? ""), role: String(x.role ?? "") }))
|
|
87
|
+
),
|
|
88
|
+
assign: (user, role) => t.post("roles", { user, role }).then(() => void 0),
|
|
89
|
+
revoke: (user, role) => t.del(`roles/${enc(user)}/${enc(role)}`)
|
|
90
|
+
},
|
|
91
|
+
summary: () => t.get("summary").then(asRecord)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/framework/fields.ts
|
|
96
|
+
var TYPE_MAP = {
|
|
97
|
+
Data: "text",
|
|
98
|
+
SmallText: "text",
|
|
99
|
+
Text: "longText",
|
|
100
|
+
LongText: "longText",
|
|
101
|
+
RichText: "richText",
|
|
102
|
+
Int: "number",
|
|
103
|
+
Float: "number",
|
|
104
|
+
Currency: "currency",
|
|
105
|
+
Check: "boolean",
|
|
106
|
+
Date: "date",
|
|
107
|
+
Datetime: "dateTime",
|
|
108
|
+
Select: "select",
|
|
109
|
+
Link: "relation",
|
|
110
|
+
Table: "json",
|
|
111
|
+
Attach: "url",
|
|
112
|
+
JSON: "json",
|
|
113
|
+
Password: "text"
|
|
114
|
+
};
|
|
115
|
+
var ACRONYMS = /* @__PURE__ */ new Set(["id", "url", "seo", "api", "ip"]);
|
|
116
|
+
function humanize(name) {
|
|
117
|
+
return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().split(/\s+/).filter(Boolean).map((w) => ACRONYMS.has(w.toLowerCase()) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
118
|
+
}
|
|
119
|
+
function selectOptions(options) {
|
|
120
|
+
return (options ?? "").split("\n").map((s) => s.replace(/\r$/, "").trim()).filter(Boolean).map((v) => ({ value: v, label: v }));
|
|
121
|
+
}
|
|
122
|
+
function metadataFor(f) {
|
|
123
|
+
switch (f.fieldtype) {
|
|
124
|
+
case "Select":
|
|
125
|
+
return { options: selectOptions(f.options) };
|
|
126
|
+
case "Currency":
|
|
127
|
+
return { currencyCode: "USD" };
|
|
128
|
+
case "Int":
|
|
129
|
+
return { decimals: 0 };
|
|
130
|
+
case "Link":
|
|
131
|
+
return { objectName: f.options };
|
|
132
|
+
default:
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function autonameSource(dt) {
|
|
137
|
+
const a = (dt.autoname ?? "").trim();
|
|
138
|
+
return a.startsWith("field:") ? a.slice("field:".length).trim() : "";
|
|
139
|
+
}
|
|
140
|
+
function docTypeToFields(dt, opts = {}) {
|
|
141
|
+
const src = autonameSource(dt);
|
|
142
|
+
return (dt.fields ?? []).filter((f) => !f.hidden).map((f) => {
|
|
143
|
+
const readOnly = f.readOnly || opts.editing && f.fieldname === src || void 0;
|
|
144
|
+
const metadata = metadataFor(f);
|
|
145
|
+
return {
|
|
146
|
+
name: f.fieldname,
|
|
147
|
+
label: f.label || humanize(f.fieldname),
|
|
148
|
+
type: TYPE_MAP[f.fieldtype] ?? "text",
|
|
149
|
+
...metadata ? { metadata } : {},
|
|
150
|
+
...readOnly ? { readOnly: true } : {}
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function statusField(dt) {
|
|
155
|
+
const f = (dt.fields ?? []).find(
|
|
156
|
+
(x) => x.fieldtype === "Select" && x.fieldname === "status" && (x.options ?? "").includes("Published")
|
|
157
|
+
);
|
|
158
|
+
return f ? f.fieldname : "";
|
|
159
|
+
}
|
|
160
|
+
function listHiddenFields(dt) {
|
|
161
|
+
const anyListed = (dt.fields ?? []).some((f) => f.inListView);
|
|
162
|
+
if (!anyListed) return [];
|
|
163
|
+
return (dt.fields ?? []).filter((f) => !f.inListView && !f.hidden).map((f) => f.fieldname);
|
|
164
|
+
}
|
|
165
|
+
function cardFields(dt, fields, max = 4) {
|
|
166
|
+
const title = dt.titleField ?? "";
|
|
167
|
+
const listed = new Set(listedFieldNames(dt));
|
|
168
|
+
const pick = listed.size ? fields.filter((f) => listed.has(f.name)) : fields;
|
|
169
|
+
return pick.filter((f) => f.name !== title).slice(0, max);
|
|
170
|
+
}
|
|
171
|
+
function listedFieldNames(dt) {
|
|
172
|
+
return (dt.fields ?? []).filter((f) => f.inListView && !f.hidden).map((f) => f.fieldname);
|
|
173
|
+
}
|
|
174
|
+
function isMediaDoctype(dt) {
|
|
175
|
+
return (dt.fields ?? []).some((f) => f.fieldtype === "Attach" && f.reqd);
|
|
176
|
+
}
|
|
177
|
+
function mediaFileField(dt) {
|
|
178
|
+
const f = (dt.fields ?? []).find((x) => x.fieldtype === "Attach" && x.reqd) ?? (dt.fields ?? []).find((x) => x.fieldtype === "Attach");
|
|
179
|
+
return f ? f.fieldname : "";
|
|
180
|
+
}
|
|
181
|
+
function mediaDocPayload(dt, facts) {
|
|
182
|
+
const declared = new Set((dt.fields ?? []).map((f) => f.fieldname));
|
|
183
|
+
const out = {};
|
|
184
|
+
const fileField = mediaFileField(dt);
|
|
185
|
+
if (fileField) out[fileField] = facts.fileRef;
|
|
186
|
+
const titleF = dt.titleField && declared.has(dt.titleField) ? dt.titleField : declared.has("title") ? "title" : "";
|
|
187
|
+
if (titleF && titleF !== fileField) out[titleF] = facts.filename;
|
|
188
|
+
const optional = [
|
|
189
|
+
["mime", facts.mime],
|
|
190
|
+
["size", facts.size],
|
|
191
|
+
["width", facts.width],
|
|
192
|
+
["height", facts.height]
|
|
193
|
+
];
|
|
194
|
+
for (const [k, v] of optional) if (declared.has(k) && k !== fileField && k !== titleF) out[k] = v;
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
function moduleDoctypes(dts, module) {
|
|
198
|
+
return dts.filter((dt) => (dt.module ?? "") === module);
|
|
199
|
+
}
|
|
200
|
+
var PROJECT_FIELD = "project";
|
|
201
|
+
function hasProjectField(dt) {
|
|
202
|
+
return (dt.fields ?? []).some((f) => f.fieldname === PROJECT_FIELD);
|
|
203
|
+
}
|
|
204
|
+
function isDraft(record) {
|
|
205
|
+
return Number(record?.docstatus ?? DRAFT) === DRAFT;
|
|
206
|
+
}
|
|
207
|
+
function titleOf(doc, dt) {
|
|
208
|
+
if (dt.titleField && typeof doc[dt.titleField] === "string" && doc[dt.titleField]) {
|
|
209
|
+
return String(doc[dt.titleField]);
|
|
210
|
+
}
|
|
211
|
+
return typeof doc.name === "string" && doc.name ? doc.name : "(untitled)";
|
|
212
|
+
}
|
|
213
|
+
function slugify(s) {
|
|
214
|
+
return String(s ?? "").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
215
|
+
}
|
|
216
|
+
function isValidDoctypeName(name) {
|
|
217
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name.trim());
|
|
218
|
+
}
|
|
219
|
+
var secToMs = (v) => {
|
|
220
|
+
const n = Number(v);
|
|
221
|
+
return Number.isFinite(n) && n > 0 ? n * 1e3 : void 0;
|
|
222
|
+
};
|
|
223
|
+
var numOrNull = (v) => {
|
|
224
|
+
const n = Number(v);
|
|
225
|
+
return Number.isFinite(n) ? n : null;
|
|
226
|
+
};
|
|
227
|
+
function coerceForDisplay(f, v) {
|
|
228
|
+
switch (f.fieldtype) {
|
|
229
|
+
case "Currency":
|
|
230
|
+
return { amount: numOrNull(v), currencyCode: "USD" };
|
|
231
|
+
case "Check":
|
|
232
|
+
return v === 1 || v === true || v === "1";
|
|
233
|
+
case "Datetime":
|
|
234
|
+
return typeof v === "string" ? v.replace(" ", "T") : v;
|
|
235
|
+
case "Password":
|
|
236
|
+
return v === REDACTED_MARKER ? "" : v;
|
|
237
|
+
// never surface the marker
|
|
238
|
+
default:
|
|
239
|
+
return v;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function toRecord(doc, dt) {
|
|
243
|
+
const rec = {
|
|
244
|
+
id: doc.name,
|
|
245
|
+
name: doc.name,
|
|
246
|
+
doctype: doc.doctype,
|
|
247
|
+
docstatus: doc.docstatus,
|
|
248
|
+
createdAt: secToMs(doc.createdAt),
|
|
249
|
+
updatedAt: secToMs(doc.updatedAt)
|
|
250
|
+
};
|
|
251
|
+
for (const f of dt.fields ?? []) {
|
|
252
|
+
if (!(f.fieldname in doc)) continue;
|
|
253
|
+
rec[f.fieldname] = coerceForDisplay(f, doc[f.fieldname]);
|
|
254
|
+
}
|
|
255
|
+
return rec;
|
|
256
|
+
}
|
|
257
|
+
function coerceForWrite(f, v) {
|
|
258
|
+
switch (f.fieldtype) {
|
|
259
|
+
case "Currency":
|
|
260
|
+
return v && typeof v === "object" && "amount" in v ? Number(v.amount) : Number(v);
|
|
261
|
+
case "Check":
|
|
262
|
+
return Boolean(v);
|
|
263
|
+
case "Int":
|
|
264
|
+
case "Float":
|
|
265
|
+
return Number(v);
|
|
266
|
+
case "Datetime":
|
|
267
|
+
if (v instanceof Date) return v.toISOString();
|
|
268
|
+
if (typeof v === "number" && Number.isFinite(v)) return new Date(v).toISOString();
|
|
269
|
+
return v;
|
|
270
|
+
case "Link":
|
|
271
|
+
if (v == null || v === "") return "";
|
|
272
|
+
if (typeof v === "object") {
|
|
273
|
+
return String(v.id ?? v.value ?? "");
|
|
274
|
+
}
|
|
275
|
+
return v;
|
|
276
|
+
default:
|
|
277
|
+
return v;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function enrichLinks(record, dt, optionsByField) {
|
|
281
|
+
const out = { ...record };
|
|
282
|
+
for (const f of dt.fields ?? []) {
|
|
283
|
+
if (f.fieldtype !== "Link") continue;
|
|
284
|
+
const raw = out[f.fieldname];
|
|
285
|
+
if (typeof raw === "string" && raw) {
|
|
286
|
+
const opt = (optionsByField[f.fieldname] ?? []).find((o) => o.value === raw);
|
|
287
|
+
out[f.fieldname] = { id: raw, label: opt?.label ?? raw };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
function linkFields(dt) {
|
|
293
|
+
return (dt.fields ?? []).filter((f) => f.fieldtype === "Link");
|
|
294
|
+
}
|
|
295
|
+
function newDraft(dt) {
|
|
296
|
+
const out = {};
|
|
297
|
+
for (const f of dt.fields ?? []) {
|
|
298
|
+
if (f.default === void 0 || f.default === "") continue;
|
|
299
|
+
out[f.fieldname] = f.fieldtype === "Check" ? f.default === "1" || f.default === "true" : f.default;
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
function savePayload(draft, dt) {
|
|
304
|
+
const src = autonameSource(dt);
|
|
305
|
+
const out = {};
|
|
306
|
+
for (const f of dt.fields ?? []) {
|
|
307
|
+
if (f.readOnly) continue;
|
|
308
|
+
let v = draft[f.fieldname];
|
|
309
|
+
if (v === void 0) continue;
|
|
310
|
+
v = coerceForWrite(f, v);
|
|
311
|
+
if (f.fieldname === src && typeof v === "string") v = slugify(v);
|
|
312
|
+
out[f.fieldname] = v;
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/framework/builder-logic.ts
|
|
318
|
+
var BUILDER_FIELD_TYPES = [
|
|
319
|
+
{ type: "Data", label: "Text", hint: "Single line of text" },
|
|
320
|
+
{ type: "RichText", label: "Rich text", hint: "Formatted body (WYSIWYG)" },
|
|
321
|
+
{ type: "LongText", label: "Long text", hint: "Multi-line plain text" },
|
|
322
|
+
{ type: "Int", label: "Number", hint: "Whole number" },
|
|
323
|
+
{ type: "Float", label: "Decimal", hint: "Decimal number" },
|
|
324
|
+
{ type: "Currency", label: "Currency", hint: "Money amount" },
|
|
325
|
+
{ type: "Check", label: "Checkbox", hint: "True / false" },
|
|
326
|
+
{ type: "Date", label: "Date", hint: "Calendar date" },
|
|
327
|
+
{ type: "Datetime", label: "Date & time", hint: "Date with time" },
|
|
328
|
+
{ type: "Select", label: "Select", hint: "One of a list of options" },
|
|
329
|
+
{ type: "Link", label: "Relation", hint: "Reference another collection" },
|
|
330
|
+
{ type: "Attach", label: "Attachment", hint: "A file / image URL" },
|
|
331
|
+
{ type: "Table", label: "Table", hint: "Repeatable child rows (JSON)" },
|
|
332
|
+
{ type: "JSON", label: "JSON", hint: "Arbitrary JSON" }
|
|
333
|
+
];
|
|
334
|
+
var LABELS = Object.fromEntries(
|
|
335
|
+
BUILDER_FIELD_TYPES.map((t) => [t.type, t.label])
|
|
336
|
+
);
|
|
337
|
+
function fieldTypeLabel(t) {
|
|
338
|
+
return LABELS[t] ?? t;
|
|
339
|
+
}
|
|
340
|
+
function fieldNeedsOptions(t) {
|
|
341
|
+
return t === "Select" || t === "Link" || t === "Table";
|
|
342
|
+
}
|
|
343
|
+
var seq = 0;
|
|
344
|
+
function newKey() {
|
|
345
|
+
seq += 1;
|
|
346
|
+
return `f${seq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
347
|
+
}
|
|
348
|
+
function blankField(type = "Data") {
|
|
349
|
+
return { key: newKey(), label: "", type, required: false, inListView: false, options: "" };
|
|
350
|
+
}
|
|
351
|
+
function starterFields() {
|
|
352
|
+
return [
|
|
353
|
+
{ key: newKey(), label: "Title", type: "Data", required: true, inListView: true, options: "" },
|
|
354
|
+
{ key: newKey(), label: "Slug", type: "Data", required: true, inListView: true, options: "" },
|
|
355
|
+
{ key: newKey(), label: "Body", type: "RichText", required: false, inListView: false, options: "" },
|
|
356
|
+
{ key: newKey(), label: "Status", type: "Select", required: false, inListView: true, options: "Draft\nPublished" },
|
|
357
|
+
// Optional project scope — lets a host's org→project switcher filter this collection.
|
|
358
|
+
{ key: newKey(), label: "Project", type: "Data", required: false, inListView: false, options: "" }
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
function fieldsFromDocType(dt) {
|
|
362
|
+
return (dt.fields ?? []).map((f) => ({
|
|
363
|
+
key: newKey(),
|
|
364
|
+
label: f.label || f.fieldname,
|
|
365
|
+
type: f.fieldtype,
|
|
366
|
+
required: Boolean(f.reqd),
|
|
367
|
+
inListView: Boolean(f.inListView),
|
|
368
|
+
options: f.options ?? ""
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
function fieldNameFromLabel(label) {
|
|
372
|
+
const slug = slugify(label).replace(/-/g, "_");
|
|
373
|
+
return /^[0-9]/.test(slug) ? `f_${slug}` : slug;
|
|
374
|
+
}
|
|
375
|
+
function validateBuilder(name, fields) {
|
|
376
|
+
const fieldErrors = {};
|
|
377
|
+
let formError = null;
|
|
378
|
+
if (!isValidDoctypeName(name.trim())) {
|
|
379
|
+
formError = "Enter a collection name with letters, digits, dashes or underscores (no spaces).";
|
|
380
|
+
} else if (fields.length === 0) {
|
|
381
|
+
formError = "Add at least one field.";
|
|
382
|
+
}
|
|
383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
384
|
+
for (const f of fields) {
|
|
385
|
+
const label = f.label.trim();
|
|
386
|
+
if (!label) {
|
|
387
|
+
fieldErrors[f.key] = "Name this field.";
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const fname = fieldNameFromLabel(label);
|
|
391
|
+
if (!fname) {
|
|
392
|
+
fieldErrors[f.key] = "Use letters or digits in the field name.";
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (seen.has(fname)) {
|
|
396
|
+
fieldErrors[f.key] = `Duplicate field "${fname}".`;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
seen.add(fname);
|
|
400
|
+
if ((f.type === "Link" || f.type === "Table") && !f.options.trim()) {
|
|
401
|
+
fieldErrors[f.key] = f.type === "Link" ? "Choose the collection to relate to." : "Name the child collection.";
|
|
402
|
+
} else if (f.type === "Select" && !f.options.trim()) {
|
|
403
|
+
fieldErrors[f.key] = "Add at least one option.";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return { ok: !formError && Object.keys(fieldErrors).length === 0, fieldErrors, formError };
|
|
407
|
+
}
|
|
408
|
+
function toDocField(f) {
|
|
409
|
+
const fieldname = fieldNameFromLabel(f.label);
|
|
410
|
+
const field = {
|
|
411
|
+
fieldname,
|
|
412
|
+
fieldtype: f.type,
|
|
413
|
+
label: f.label.trim()
|
|
414
|
+
};
|
|
415
|
+
if (f.required) field.reqd = true;
|
|
416
|
+
if (f.inListView) field.inListView = true;
|
|
417
|
+
if (fieldNeedsOptions(f.type) && f.options.trim()) field.options = f.options.trim();
|
|
418
|
+
return field;
|
|
419
|
+
}
|
|
420
|
+
function moveField(fields, key, dir) {
|
|
421
|
+
const i = fields.findIndex((f) => f.key === key);
|
|
422
|
+
const j = i + dir;
|
|
423
|
+
if (i < 0 || j < 0 || j >= fields.length) return fields;
|
|
424
|
+
const next = [...fields];
|
|
425
|
+
[next[i], next[j]] = [next[j], next[i]];
|
|
426
|
+
return next;
|
|
427
|
+
}
|
|
428
|
+
function toDocType(name, module, fields) {
|
|
429
|
+
const docFields = fields.map(toDocField);
|
|
430
|
+
const byName = new Set(docFields.map((f) => f.fieldname));
|
|
431
|
+
const dt = { name: name.trim(), module, fields: docFields };
|
|
432
|
+
if (byName.has("slug")) dt.autoname = "field:slug";
|
|
433
|
+
if (byName.has("title")) dt.titleField = "title";
|
|
434
|
+
return dt;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/framework/media.ts
|
|
438
|
+
var REF_PREFIX = "s3://";
|
|
439
|
+
function mediaRef(bucket, key) {
|
|
440
|
+
return `${REF_PREFIX}${bucket}/${key}`;
|
|
441
|
+
}
|
|
442
|
+
function isRef(value) {
|
|
443
|
+
return typeof value === "string" && value.startsWith(REF_PREFIX);
|
|
444
|
+
}
|
|
445
|
+
function parseRef(ref) {
|
|
446
|
+
if (!ref.startsWith(REF_PREFIX)) return null;
|
|
447
|
+
const rest = ref.slice(REF_PREFIX.length);
|
|
448
|
+
const slash = rest.indexOf("/");
|
|
449
|
+
if (slash <= 0) return null;
|
|
450
|
+
return { bucket: rest.slice(0, slash), key: rest.slice(slash + 1) };
|
|
451
|
+
}
|
|
452
|
+
function joinKey(...parts) {
|
|
453
|
+
return parts.filter(Boolean).join("/");
|
|
454
|
+
}
|
|
455
|
+
function mediaKey(filename, folder = "") {
|
|
456
|
+
const dot = filename.lastIndexOf(".");
|
|
457
|
+
const ext = dot > 0 ? filename.slice(dot + 1).toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
458
|
+
const base = slugify(dot > 0 ? filename.slice(0, dot) : filename) || "file";
|
|
459
|
+
const stamp = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
460
|
+
const name = ext ? `${base}-${stamp}.${ext}` : `${base}-${stamp}`;
|
|
461
|
+
return folder ? joinKey(slugify(folder), name) : name;
|
|
462
|
+
}
|
|
463
|
+
function looksLikeImage(value) {
|
|
464
|
+
return /\.(png|jpe?g|gif|webp|svg|avif)(\?|#|$)/i.test(value) || value.startsWith("data:image/");
|
|
465
|
+
}
|
|
466
|
+
function createMediaUploader(store, bucket) {
|
|
467
|
+
return {
|
|
468
|
+
bucket,
|
|
469
|
+
async upload(file, folder = "") {
|
|
470
|
+
await store.ensureBucket(bucket).catch(() => void 0);
|
|
471
|
+
const key = mediaKey(file.name, folder);
|
|
472
|
+
const presigned = await store.presignUpload(bucket, key);
|
|
473
|
+
if (!presigned || (presigned.method ?? "PUT") !== "PUT" || !presigned.url) {
|
|
474
|
+
throw new Error("Could not start the upload (object storage not configured).");
|
|
475
|
+
}
|
|
476
|
+
await store.put(presigned.url, file);
|
|
477
|
+
const { width, height } = await imageDimensions(file);
|
|
478
|
+
return {
|
|
479
|
+
fileRef: mediaRef(bucket, key),
|
|
480
|
+
filename: file.name,
|
|
481
|
+
mime: file.type || "application/octet-stream",
|
|
482
|
+
size: file.size,
|
|
483
|
+
width,
|
|
484
|
+
height
|
|
485
|
+
};
|
|
486
|
+
},
|
|
487
|
+
async resolveUrl(value) {
|
|
488
|
+
if (typeof value !== "string" || value === "") return "";
|
|
489
|
+
if (!isRef(value)) return value;
|
|
490
|
+
const parsed = parseRef(value);
|
|
491
|
+
if (!parsed) return "";
|
|
492
|
+
const presigned = await store.presignDownload(parsed.bucket, parsed.key).catch(() => null);
|
|
493
|
+
return presigned?.url ?? "";
|
|
494
|
+
},
|
|
495
|
+
async remove(value) {
|
|
496
|
+
if (!isRef(value)) return;
|
|
497
|
+
const parsed = parseRef(value);
|
|
498
|
+
if (!parsed) return;
|
|
499
|
+
await store.deleteObject(parsed.bucket, parsed.key).catch(() => void 0);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function imageDimensions(file) {
|
|
504
|
+
return new Promise((resolve) => {
|
|
505
|
+
if (typeof window === "undefined" || typeof Image === "undefined" || !file.type.startsWith("image/")) {
|
|
506
|
+
resolve({ width: 0, height: 0 });
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const url = URL.createObjectURL(file);
|
|
510
|
+
const img = new Image();
|
|
511
|
+
img.onload = () => {
|
|
512
|
+
resolve({ width: img.naturalWidth, height: img.naturalHeight });
|
|
513
|
+
URL.revokeObjectURL(url);
|
|
514
|
+
};
|
|
515
|
+
img.onerror = () => {
|
|
516
|
+
resolve({ width: 0, height: 0 });
|
|
517
|
+
URL.revokeObjectURL(url);
|
|
518
|
+
};
|
|
519
|
+
img.src = url;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/framework/data.ts
|
|
524
|
+
async function loadLinkOptions(client, dt) {
|
|
525
|
+
const links = linkFields(dt);
|
|
526
|
+
const out = {};
|
|
527
|
+
await Promise.all(
|
|
528
|
+
links.map(async (f) => {
|
|
529
|
+
const target = f.options;
|
|
530
|
+
if (!target) return;
|
|
531
|
+
const [tdt, docs] = await Promise.all([
|
|
532
|
+
client.doctypes.get(target).catch(() => null),
|
|
533
|
+
client.records.list(target, { limit: 200 }).catch(() => [])
|
|
534
|
+
]);
|
|
535
|
+
out[f.fieldname] = docs.map((d) => ({
|
|
536
|
+
value: String(d.name),
|
|
537
|
+
label: tdt ? titleOf(d, tdt) : String(d.name)
|
|
538
|
+
}));
|
|
539
|
+
})
|
|
540
|
+
);
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
function makeFieldOptions(linkOptions) {
|
|
544
|
+
return (field) => {
|
|
545
|
+
if (field.type === "relation") return linkOptions[field.name] ?? [];
|
|
546
|
+
if (field.type === "select" || field.type === "multiSelect") {
|
|
547
|
+
return field.metadata?.options;
|
|
548
|
+
}
|
|
549
|
+
return void 0;
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export { BUILDER_FIELD_TYPES, CANCELLED, DRAFT, PROJECT_FIELD, REDACTED_MARKER, SUBMITTED, autonameSource, blankField, cardFields, createFrameworkClient, createMediaUploader, docTypeToFields, enrichLinks, fieldNameFromLabel, fieldNeedsOptions, fieldTypeLabel, fieldsFromDocType, hasProjectField, humanize, imageDimensions, isDraft, isMediaDoctype, isRef, isValidDoctypeName, joinKey, linkFields, listHiddenFields, listQuery, listedFieldNames, loadLinkOptions, looksLikeImage, makeFieldOptions, mediaDocPayload, mediaFileField, mediaKey, mediaRef, moduleDoctypes, moveField, newDraft, newKey, parseRef, rows, savePayload, slugify, starterFields, statusField, titleOf, toDocField, toDocType, toRecord, validateBuilder };
|
|
554
|
+
//# sourceMappingURL=chunk-P2QU5MN5.js.map
|
|
555
|
+
//# sourceMappingURL=chunk-P2QU5MN5.js.map
|