@aduptive/strapi-blockscene 2.0.0-alpha.3 → 2.0.0-alpha.5
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 +282 -23
- package/dist/admin.cjs +4907 -750
- package/dist/admin.mjs +4939 -746
- package/dist/server.cjs +357 -33
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -118,6 +118,7 @@ var require_settings = __commonJS({
|
|
|
118
118
|
var TEMPLATES2 = ["generic", "banner", "cards", "imageText", "faq", "none"];
|
|
119
119
|
var INITIAL_STATES = ["closed", "open", "remember"];
|
|
120
120
|
var PREVIEW_MODES = ["form", "split", "preview"];
|
|
121
|
+
var HIDDEN_MODES = ["strip", "flag", "off"];
|
|
121
122
|
var DEFAULTS2 = {
|
|
122
123
|
palette: { background: "#F6F6F9", surface: "#DCDCE4", text: "#32324D", accent: "#4945FF" },
|
|
123
124
|
components: {},
|
|
@@ -125,11 +126,88 @@ var require_settings = __commonJS({
|
|
|
125
126
|
// blockPreviewInForm: compact read-only preview above a block's fields in form
|
|
126
127
|
// mode. Only installations that ship the accordion integration (server config
|
|
127
128
|
// `blockPreview: true`) show and honour it; the page preview is the editor.
|
|
128
|
-
editor: {
|
|
129
|
+
editor: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
showOpenAll: true,
|
|
132
|
+
showCloseAll: true,
|
|
133
|
+
showRowThumbnails: true,
|
|
134
|
+
initialState: "closed",
|
|
135
|
+
previewMode: "form",
|
|
136
|
+
previewUrl: "",
|
|
137
|
+
blockPreviewUrl: "",
|
|
138
|
+
blockPreviewInForm: false,
|
|
139
|
+
// Strapi 5: rich-text custom fields render a read-only preview until clicked (see admin/LazyInput.tsx).
|
|
140
|
+
lazyEditors: true,
|
|
141
|
+
lazyFields: ["plugin::ckeditor5.CKEditor"],
|
|
142
|
+
// Row actions in each block header (see README "Row actions"). hiddenBlocks: what the content API does with rows
|
|
143
|
+
// hidden on the site: 'strip' removes them, 'flag' sends them with the attribute, 'off' hides the eye icon.
|
|
144
|
+
confirmDelete: true,
|
|
145
|
+
hiddenBlocks: "strip",
|
|
146
|
+
duplicate: true,
|
|
147
|
+
clipboard: true
|
|
148
|
+
},
|
|
149
|
+
// Per content type (only the ones with a Dynamic Zone): { enabled: false } turns the plugin off there;
|
|
150
|
+
// previewMode overrides editor.previewMode as the mode the edit view opens in. Absent means the global behaviour.
|
|
151
|
+
contentTypes: {}
|
|
152
|
+
};
|
|
153
|
+
var ICONS = ["text", "tag", "seo", "settings", "image", "link", "palette", "list", "globe", "info"];
|
|
154
|
+
var SIDEBAR_POSITIONS = ["left", "right", "bottom"];
|
|
155
|
+
var OPENS = ["modal", "drawer"];
|
|
156
|
+
var LABEL = /^[^<>]{1,40}$/;
|
|
157
|
+
var sidebarItem = (item, attributes) => item && typeof item === "object" && !Array.isArray(item) && Object.keys(item).every((key) => ["label", "icon", "open", "fields"].includes(key)) && typeof item.label === "string" && LABEL.test(item.label.trim()) && (item.icon === void 0 || item.icon === "" || ICONS.includes(item.icon)) && OPENS.includes(item.open) && Array.isArray(item.fields) && item.fields.length > 0 && item.fields.length <= 30 && item.fields.every((field) => typeof field === "string" && (!attributes || attributes.includes(field)));
|
|
158
|
+
var TYPE_KEYS = {
|
|
159
|
+
enabled: (value) => typeof value === "boolean",
|
|
160
|
+
previewMode: (value) => PREVIEW_MODES.includes(value),
|
|
161
|
+
sidebarPosition: (value) => SIDEBAR_POSITIONS.includes(value),
|
|
162
|
+
sidebar: (value, attributes) => Array.isArray(value) && value.length <= 12 && value.every((item) => sidebarItem(item, attributes))
|
|
129
163
|
};
|
|
164
|
+
var typeMap = (types) => Array.isArray(types) ? Object.fromEntries(types.map((uid) => [uid, null])) : types || {};
|
|
130
165
|
var PREVIEW_URL = /^https?:\/\/[^\s"'<>]{1,500}$/;
|
|
166
|
+
var blockPreviewUrl = (value) => typeof value === "string" && (value === "" || PREVIEW_URL.test(value) && !/[{}]/.test(value.replace(/\{(uid|name|category|variant|locale)\}/g, "")) && URL.canParse?.(value.replace(/[{}]/g, "")) !== false);
|
|
131
167
|
var COLOR = /^#[0-9A-Fa-f]{6}$/;
|
|
168
|
+
var FIELD_UID = /^(plugin|global)::[\w.-]+$/;
|
|
169
|
+
var lazyFields = (value) => Array.isArray(value) && value.length <= 20 && value.every((uid) => typeof uid === "string" && FIELD_UID.test(uid));
|
|
132
170
|
var UID = /^[a-z0-9-]+\.[a-z0-9-]+$/;
|
|
171
|
+
var TYPOLOGIES2 = ["hero", "text", "media", "listing", "cards", "cta", "form", "layout"];
|
|
172
|
+
var TAG = /^[^<>\n]{1,24}$/;
|
|
173
|
+
var cleanTags = (value) => Array.isArray(value) ? [...new Set(value.filter((tag) => typeof tag === "string" && TAG.test(tag.trim())).map((tag) => tag.trim()))].slice(0, 10) : [];
|
|
174
|
+
var validTags = (value) => Array.isArray(value) && value.length <= 10 && value.every((tag) => typeof tag === "string" && TAG.test(tag.trim()));
|
|
175
|
+
var GUESSES = [
|
|
176
|
+
["hero", /hero|banner|cover/],
|
|
177
|
+
["text", /text|rich|quote|title/],
|
|
178
|
+
["media", /image|media|video|gallery|carousel/],
|
|
179
|
+
["listing", /list|query|archive|related|posts|projects/],
|
|
180
|
+
["cards", /card/],
|
|
181
|
+
["cta", /cta|button|link/],
|
|
182
|
+
["form", /form|contact/],
|
|
183
|
+
["layout", /wrapper|column|grid|divider|divisor|spacer|section/]
|
|
184
|
+
];
|
|
185
|
+
function guessTypology(uid, displayName = "") {
|
|
186
|
+
const name = `${uid.split(".").pop()} ${displayName}`.toLowerCase();
|
|
187
|
+
return GUESSES.find(([, pattern]) => pattern.test(name))?.[0] || "text";
|
|
188
|
+
}
|
|
189
|
+
function facetsOf(uid, schema, schemas = {}) {
|
|
190
|
+
const facets = /* @__PURE__ */ new Set();
|
|
191
|
+
const read = (attributes, nested) => {
|
|
192
|
+
for (const attr of Object.values(attributes || {})) {
|
|
193
|
+
if (attr?.type === "media") {
|
|
194
|
+
const allowed = attr.allowedTypes;
|
|
195
|
+
if (!allowed || allowed.includes("images")) facets.add("image");
|
|
196
|
+
if (!allowed || allowed.includes("videos")) facets.add("video");
|
|
197
|
+
if (attr.multiple) facets.add("gallery");
|
|
198
|
+
} else if (["richtext", "blocks"].includes(attr?.type) || attr?.type === "customField" && /ckeditor/i.test(String(attr.customField))) facets.add("richtext");
|
|
199
|
+
else if (attr?.type === "relation") facets.add("dynamic");
|
|
200
|
+
else if (attr?.type === "component") {
|
|
201
|
+
if (attr.repeatable) facets.add("list");
|
|
202
|
+
if (!nested) read(schemas[attr.component]?.attributes, true);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
read(schema?.attributes, false);
|
|
207
|
+
if (/query|archive|related/i.test(uid)) facets.add("dynamic");
|
|
208
|
+
if (/form|contact/i.test(`${uid} ${schema?.info?.displayName || ""}`)) facets.add("form");
|
|
209
|
+
return [...facets];
|
|
210
|
+
}
|
|
133
211
|
var safeUrl2 = (value) => typeof value === "string" && (/^\/(?!\/)/.test(value) || /^https?:\/\//.test(value)) ? value : void 0;
|
|
134
212
|
var { safeGroups: safeGroups2 } = require_groups();
|
|
135
213
|
var safeVersion = (value) => typeof value === "string" && /^[\w.-]{1,32}$/.test(value) ? value : void 0;
|
|
@@ -156,6 +234,15 @@ var require_settings = __commonJS({
|
|
|
156
234
|
["label", "description", "category", "keywords"].filter((key) => typeof entry[key] === "string").map((key) => [key, entry[key]])
|
|
157
235
|
);
|
|
158
236
|
entries[uid].image = safeUrl2(entry.image);
|
|
237
|
+
if (TYPOLOGIES2.includes(entry.typology)) entries[uid].typology = entry.typology;
|
|
238
|
+
const tags = cleanTags(entry.tags);
|
|
239
|
+
if (tags.length || entries[uid].category) entries[uid].tags = tags.length ? tags : cleanTags([entries[uid].category]);
|
|
240
|
+
}
|
|
241
|
+
for (const [uid, schema] of Object.entries(config.schemas || {})) {
|
|
242
|
+
const entry = entries[uid] ||= {};
|
|
243
|
+
entry.facets = facetsOf(uid, schema, config.schemas);
|
|
244
|
+
entry.typology ||= guessTypology(uid, schema?.info?.displayName);
|
|
245
|
+
entry.tags ||= [];
|
|
159
246
|
}
|
|
160
247
|
return {
|
|
161
248
|
components: entries,
|
|
@@ -167,7 +254,8 @@ var require_settings = __commonJS({
|
|
|
167
254
|
groups: safeGroups2(config.groups, config.componentUids)
|
|
168
255
|
};
|
|
169
256
|
}
|
|
170
|
-
function validateSettings2(input, componentUids2) {
|
|
257
|
+
function validateSettings2(input, componentUids2, contentTypeUids2 = []) {
|
|
258
|
+
const types = typeMap(contentTypeUids2);
|
|
171
259
|
if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Settings must be an object");
|
|
172
260
|
if (JSON.stringify(input).length > 64 * 1024) fail2("Settings payload too large");
|
|
173
261
|
const out = structuredClone(DEFAULTS2);
|
|
@@ -179,9 +267,9 @@ var require_settings = __commonJS({
|
|
|
179
267
|
}
|
|
180
268
|
for (const [key, value] of Object.entries(input.editor || {})) {
|
|
181
269
|
if (!(key in DEFAULTS2.editor)) fail2(`Unknown editor option "${key}"`);
|
|
182
|
-
const valid = key === "initialState" ? INITIAL_STATES.includes(value) : key === "previewMode" ? PREVIEW_MODES.includes(value) : key === "previewUrl" ? value === "" || typeof value === "string" && PREVIEW_URL.test(value) : typeof value === "boolean";
|
|
270
|
+
const valid = key === "initialState" ? INITIAL_STATES.includes(value) : key === "previewMode" ? PREVIEW_MODES.includes(value) : key === "hiddenBlocks" ? HIDDEN_MODES.includes(value) : key === "previewUrl" ? value === "" || typeof value === "string" && PREVIEW_URL.test(value) : key === "blockPreviewUrl" ? blockPreviewUrl(value) : key === "lazyFields" ? lazyFields(value) : typeof value === "boolean";
|
|
183
271
|
if (!valid) fail2(`Invalid value for editor option "${key}"`);
|
|
184
|
-
out.editor[key] = key === "previewUrl" && value ? String(new URL(value).href).replace(/\/$/, "") : value;
|
|
272
|
+
out.editor[key] = key === "previewUrl" && value ? String(new URL(value).href).replace(/\/$/, "") : key === "lazyFields" ? [...new Set(value)] : value;
|
|
185
273
|
}
|
|
186
274
|
const components = input.components || {};
|
|
187
275
|
if (typeof components !== "object" || Array.isArray(components)) fail2("components must be an object");
|
|
@@ -193,6 +281,12 @@ var require_settings = __commonJS({
|
|
|
193
281
|
if (key === "template") {
|
|
194
282
|
if (!TEMPLATES2.includes(entry.template)) fail2(`Unknown template for "${uid}"`);
|
|
195
283
|
clean.template = entry.template;
|
|
284
|
+
} else if (key === "typology") {
|
|
285
|
+
if (!TYPOLOGIES2.includes(entry.typology)) fail2(`Unknown typology for "${uid}"`);
|
|
286
|
+
clean.typology = entry.typology;
|
|
287
|
+
} else if (key === "tags") {
|
|
288
|
+
if (!validTags(entry.tags)) fail2(`Invalid tags for "${uid}": up to 10, each 1 to 24 characters`);
|
|
289
|
+
clean.tags = cleanTags(entry.tags);
|
|
196
290
|
} else if (key === "mediaId") {
|
|
197
291
|
if (!Number.isInteger(entry.mediaId) || entry.mediaId <= 0) fail2(`Invalid media for "${uid}"`);
|
|
198
292
|
clean.mediaId = entry.mediaId;
|
|
@@ -200,9 +294,21 @@ var require_settings = __commonJS({
|
|
|
200
294
|
}
|
|
201
295
|
if (Object.keys(clean).length) out.components[uid] = clean;
|
|
202
296
|
}
|
|
297
|
+
const perType = input.contentTypes || {};
|
|
298
|
+
if (typeof perType !== "object" || Array.isArray(perType)) fail2("contentTypes must be an object");
|
|
299
|
+
for (const [uid, entry] of Object.entries(perType)) {
|
|
300
|
+
if (!(uid in types)) fail2(`Unknown content type "${uid}"`);
|
|
301
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) fail2(`Invalid entry for "${uid}"`);
|
|
302
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
303
|
+
if (!(key in TYPE_KEYS)) fail2(`Unknown content type setting "${key}"`);
|
|
304
|
+
if (!TYPE_KEYS[key](value, types[uid])) fail2(`Invalid value for "${key}" of "${uid}"`);
|
|
305
|
+
}
|
|
306
|
+
if (Object.keys(entry).length) out.contentTypes[uid] = structuredClone(entry);
|
|
307
|
+
}
|
|
203
308
|
return out;
|
|
204
309
|
}
|
|
205
|
-
function mergeSaved2(saved, componentUids2) {
|
|
310
|
+
function mergeSaved2(saved, componentUids2, contentTypeUids2 = []) {
|
|
311
|
+
const types = typeMap(contentTypeUids2);
|
|
206
312
|
const out = structuredClone(DEFAULTS2);
|
|
207
313
|
if (!saved || typeof saved !== "object") return out;
|
|
208
314
|
for (const key of Object.keys(DEFAULTS2.palette)) if (COLOR.test(String(saved.palette?.[key]))) out.palette[key] = saved.palette[key];
|
|
@@ -211,19 +317,156 @@ var require_settings = __commonJS({
|
|
|
211
317
|
if (value === void 0 || typeof value !== typeof DEFAULTS2.editor[key]) continue;
|
|
212
318
|
if (key === "initialState" && !INITIAL_STATES.includes(value)) continue;
|
|
213
319
|
if (key === "previewMode" && !PREVIEW_MODES.includes(value)) continue;
|
|
320
|
+
if (key === "hiddenBlocks" && !HIDDEN_MODES.includes(value)) continue;
|
|
214
321
|
if (key === "previewUrl" && value && !PREVIEW_URL.test(value)) continue;
|
|
322
|
+
if (key === "blockPreviewUrl" && !blockPreviewUrl(value)) continue;
|
|
323
|
+
if (key === "lazyFields") {
|
|
324
|
+
if (!Array.isArray(value)) continue;
|
|
325
|
+
out.editor[key] = [...new Set(value.filter((uid) => lazyFields([uid])))].slice(0, 20);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
215
328
|
out.editor[key] = value;
|
|
216
329
|
}
|
|
217
|
-
for (const [uid, entry] of Object.entries(saved.components || {}))
|
|
330
|
+
for (const [uid, entry] of Object.entries(saved.components || {})) {
|
|
331
|
+
if (!componentUids2.includes(uid) || !entry || typeof entry !== "object") continue;
|
|
332
|
+
const clean = { ...entry };
|
|
333
|
+
if ("typology" in clean && !TYPOLOGIES2.includes(clean.typology)) delete clean.typology;
|
|
334
|
+
if ("tags" in clean) {
|
|
335
|
+
clean.tags = cleanTags(clean.tags);
|
|
336
|
+
if (!clean.tags.length) delete clean.tags;
|
|
337
|
+
}
|
|
338
|
+
if (Object.keys(clean).length) out.components[uid] = clean;
|
|
339
|
+
}
|
|
340
|
+
for (const [uid, entry] of Object.entries(saved.contentTypes || {})) {
|
|
341
|
+
if (!(uid in types) || !entry || typeof entry !== "object") continue;
|
|
342
|
+
const fix = (key, value) => key === "sidebar" && Array.isArray(value) ? value.filter((item) => sidebarItem(item, types[uid])) : value;
|
|
343
|
+
const clean = Object.fromEntries(Object.entries(entry).map(([key, value]) => [key, fix(key, value)]).filter(([key, value]) => TYPE_KEYS[key]?.(value, types[uid])));
|
|
344
|
+
if (Object.keys(clean).length) out.contentTypes[uid] = clean;
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
var ENTRY_KEYS = ["components", "contentTypes"];
|
|
349
|
+
function layer2(base, saved) {
|
|
350
|
+
if (!base) return saved;
|
|
351
|
+
const doc = saved && typeof saved === "object" ? saved : {};
|
|
352
|
+
return Object.fromEntries(Object.keys(DEFAULTS2).map((key) => [key, { ...base[key], ...doc[key] && typeof doc[key] === "object" ? doc[key] : {} }]));
|
|
353
|
+
}
|
|
354
|
+
function overrides2(next, base) {
|
|
355
|
+
if (!base) return next;
|
|
356
|
+
return Object.fromEntries(Object.keys(DEFAULTS2).map((key) => {
|
|
357
|
+
const entries = Object.entries(next[key] || {}).filter(([name, value]) => JSON.stringify(value) !== JSON.stringify(base[key]?.[name]));
|
|
358
|
+
if (ENTRY_KEYS.includes(key)) {
|
|
359
|
+
for (const uid of Object.keys(base[key] || {})) if (!(uid in (next[key] || {}))) entries.push([uid, {}]);
|
|
360
|
+
}
|
|
361
|
+
return [key, Object.fromEntries(entries)];
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
var PREFS = { starred: 200, recent: 20 };
|
|
365
|
+
function validatePrefs2(input, componentUids2) {
|
|
366
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Preferences must be an object");
|
|
367
|
+
const out = { starred: [], recent: [] };
|
|
368
|
+
for (const [key, value] of Object.entries(input)) {
|
|
369
|
+
if (!(key in PREFS)) fail2(`Unknown preference "${key}"`);
|
|
370
|
+
if (!Array.isArray(value) || value.length > PREFS[key] || !value.every((uid) => typeof uid === "string" && componentUids2.includes(uid))) fail2(`Invalid value for "${key}"`);
|
|
371
|
+
out[key] = [...new Set(value)];
|
|
372
|
+
}
|
|
218
373
|
return out;
|
|
219
374
|
}
|
|
220
|
-
|
|
375
|
+
var mergePrefs2 = (saved, componentUids2) => Object.fromEntries(Object.entries(PREFS).map(([key, max]) => [key, Array.isArray(saved?.[key]) ? [...new Set(saved[key].filter((uid) => componentUids2.includes(uid)))].slice(0, max) : []]));
|
|
376
|
+
module2.exports = { PLUGIN: PLUGIN2, HIDDEN_MODES, TEMPLATES: TEMPLATES2, TYPOLOGIES: TYPOLOGIES2, guessTypology, facetsOf, validatePrefs: validatePrefs2, mergePrefs: mergePrefs2, ICONS, DEFAULTS: DEFAULTS2, catalog: catalog2, validateSettings: validateSettings2, mergeSaved: mergeSaved2, layer: layer2, overrides: overrides2, safeUrl: safeUrl2, fail: fail2 };
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// server/hidden.js
|
|
381
|
+
var require_hidden = __commonJS({
|
|
382
|
+
"server/hidden.js"(exports2, module2) {
|
|
383
|
+
"use strict";
|
|
384
|
+
var NAME = /^[a-zA-Z][a-zA-Z0-9_]{0,39}$/;
|
|
385
|
+
var DEFAULT_NAME = "bsHidden";
|
|
386
|
+
var hiddenName2 = (value) => value === void 0 ? DEFAULT_NAME : value === false ? false : typeof value === "string" && NAME.test(value) ? value : null;
|
|
387
|
+
var zoneComponents = (contentTypes) => [...new Set(Object.values(contentTypes || {}).flatMap((schema) => Object.values(schema?.attributes || {}).filter((attr) => attr?.type === "dynamiczone").flatMap((attr) => attr.components || [])))];
|
|
388
|
+
function injectHidden2(components, contentTypes, name) {
|
|
389
|
+
const added = [], skipped = [];
|
|
390
|
+
for (const uid of zoneComponents(contentTypes)) {
|
|
391
|
+
const schema = components?.[uid];
|
|
392
|
+
if (!schema) continue;
|
|
393
|
+
schema.attributes ||= {};
|
|
394
|
+
const current = schema.attributes[name];
|
|
395
|
+
if (current && current.type !== "boolean") {
|
|
396
|
+
skipped.push(uid);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (!current) schema.attributes[name] = { type: "boolean", default: false, visible: false, configurable: false };
|
|
400
|
+
added.push(uid);
|
|
401
|
+
}
|
|
402
|
+
return { added, skipped };
|
|
403
|
+
}
|
|
404
|
+
function stripHidden(data, schema, schemaOf, name, depth = 0) {
|
|
405
|
+
if (!data || typeof data !== "object" || !schema || depth > 20) return data;
|
|
406
|
+
if (Array.isArray(data)) {
|
|
407
|
+
data.forEach((item) => stripHidden(item, schema, schemaOf, name, depth));
|
|
408
|
+
return data;
|
|
409
|
+
}
|
|
410
|
+
const walk = (value, target) => Array.isArray(value) ? value.forEach((item) => stripHidden(item, target, schemaOf, name, depth + 1)) : stripHidden(value, target, schemaOf, name, depth + 1);
|
|
411
|
+
for (const [key, attr] of Object.entries(schema.attributes || {})) {
|
|
412
|
+
const value = data[key];
|
|
413
|
+
if (value == null || typeof value !== "object") continue;
|
|
414
|
+
if (attr?.type === "dynamiczone" && Array.isArray(value)) {
|
|
415
|
+
data[key] = value.filter((row) => !(row && row[name] === true));
|
|
416
|
+
for (const row of data[key]) stripHidden(row, schemaOf(row?.__component), schemaOf, name, depth + 1);
|
|
417
|
+
} else if (attr?.type === "component") walk(value, schemaOf(attr.component));
|
|
418
|
+
else if (attr?.type === "relation" && attr.target) walk(value, schemaOf(attr.target));
|
|
419
|
+
}
|
|
420
|
+
return data;
|
|
421
|
+
}
|
|
422
|
+
var contentApi = (strapi) => {
|
|
423
|
+
try {
|
|
424
|
+
return strapi.requestContext?.get?.()?.state?.route?.info?.type === "content-api";
|
|
425
|
+
} catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
var READS = ["findMany", "findOne", "findFirst"];
|
|
430
|
+
function registerHiddenStrip2(strapi, name, plugin) {
|
|
431
|
+
if (!name) return;
|
|
432
|
+
const schemaOf = (uid) => uid && (strapi.getModel?.(uid) || strapi.components?.[uid] || strapi.contentTypes?.[uid]);
|
|
433
|
+
const stripping = async () => {
|
|
434
|
+
try {
|
|
435
|
+
return (await plugin.service("settings").get()).editor.hiddenBlocks === "strip";
|
|
436
|
+
} catch {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const apply = async (uid, result) => result && contentApi(strapi) && await stripping() ? stripHidden(result, schemaOf(uid), schemaOf, name) : result;
|
|
441
|
+
if (strapi.documents?.use) {
|
|
442
|
+
strapi.documents.use(async (ctx, next) => {
|
|
443
|
+
const result = await next();
|
|
444
|
+
return READS.includes(ctx.action) ? apply(ctx.uid, result) : result;
|
|
445
|
+
});
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
strapi.entityService?.decorate?.((service) => ({
|
|
449
|
+
async findMany(uid, params) {
|
|
450
|
+
return apply(uid, await service.findMany(uid, params));
|
|
451
|
+
},
|
|
452
|
+
async findOne(uid, id, params) {
|
|
453
|
+
return apply(uid, await service.findOne(uid, id, params));
|
|
454
|
+
},
|
|
455
|
+
async findPage(uid, params) {
|
|
456
|
+
const page = await service.findPage(uid, params);
|
|
457
|
+
if (page?.results) await apply(uid, page.results);
|
|
458
|
+
return page;
|
|
459
|
+
}
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
module2.exports = { DEFAULT_NAME, hiddenName: hiddenName2, zoneComponents, injectHidden: injectHidden2, stripHidden, registerHiddenStrip: registerHiddenStrip2 };
|
|
221
463
|
}
|
|
222
464
|
});
|
|
223
465
|
|
|
224
466
|
// server/index.js
|
|
225
|
-
var { PLUGIN, TEMPLATES, DEFAULTS, catalog, validateSettings, mergeSaved, safeUrl, fail } = require_settings();
|
|
467
|
+
var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
|
|
226
468
|
var { safeGroups, validateGroups } = require_groups();
|
|
469
|
+
var { hiddenName, injectHidden, registerHiddenStrip } = require_hidden();
|
|
227
470
|
var store = (strapi) => strapi.store({ type: "plugin", name: PLUGIN });
|
|
228
471
|
var LEGACY_PLUGIN = "block-picker";
|
|
229
472
|
async function readSettings(strapi) {
|
|
@@ -247,6 +490,7 @@ async function resolveMedia(strapi, settings) {
|
|
|
247
490
|
}
|
|
248
491
|
return resolved;
|
|
249
492
|
}
|
|
493
|
+
var contentTypeUids = (strapi) => Object.fromEntries(Object.entries(strapi.contentTypes || {}).filter(([uid, schema]) => uid.startsWith("api::") && zonesOf(schema).length).map(([uid, schema]) => [uid, Object.keys(schema.attributes || {})]));
|
|
250
494
|
var zonesOf = (schema) => Object.entries(schema?.attributes || {}).filter(([, attr]) => attr?.type === "dynamiczone").map(([name]) => name);
|
|
251
495
|
var label = (error) => `row ${error.index + 1}: ${error.code === "closeBeforeOpen" ? `close marker ${error.uid} has no open marker before it` : error.code === "mismatch" ? `close marker ${error.uid} does not match the open group ${error.open} (expected ${error.expected})` : `group ${error.uid} is not closed (expected ${error.expected})`}`;
|
|
252
496
|
function checkZones(strapi, uid, entry, groups) {
|
|
@@ -263,6 +507,11 @@ function registerPublishGuard(strapi) {
|
|
|
263
507
|
const groups = safeGroups(plugin.config("groups"), componentUids(strapi));
|
|
264
508
|
if (plugin.config("groups") && !groups) strapi.log.warn(`[${PLUGIN}] "groups" config ignored: expected { "<open component uid>": "<close component uid>" } with existing components; every block is treated as ordinary.`);
|
|
265
509
|
if (!groups) return;
|
|
510
|
+
const active = async (uid) => {
|
|
511
|
+
if (plugin.config("disabled") === true) return false;
|
|
512
|
+
const settings = await plugin.service("settings").get();
|
|
513
|
+
return settings.editor.enabled && settings.contentTypes?.[uid]?.enabled !== false;
|
|
514
|
+
};
|
|
266
515
|
if (strapi.documents?.use) {
|
|
267
516
|
const defaultLocale = async () => {
|
|
268
517
|
try {
|
|
@@ -277,7 +526,7 @@ function registerPublishGuard(strapi) {
|
|
|
277
526
|
if (!publishing || !ctx.uid) return next();
|
|
278
527
|
const model = strapi.getModel(ctx.uid);
|
|
279
528
|
const zones = zonesOf(model);
|
|
280
|
-
if (!zones.length) return next();
|
|
529
|
+
if (!zones.length || !await active(ctx.uid)) return next();
|
|
281
530
|
if (action === "create") {
|
|
282
531
|
checkZones(strapi, ctx.uid, params.data || {}, groups);
|
|
283
532
|
return next();
|
|
@@ -303,19 +552,19 @@ function registerPublishGuard(strapi) {
|
|
|
303
552
|
const published = (data) => Boolean(data && data.publishedAt);
|
|
304
553
|
strapi.db.lifecycles.subscribe({
|
|
305
554
|
async beforeCreate(event) {
|
|
306
|
-
if (published(event.params?.data)) checkZones(strapi, event.model.uid, event.params.data, groups);
|
|
555
|
+
if (published(event.params?.data) && await active(event.model.uid)) checkZones(strapi, event.model.uid, event.params.data, groups);
|
|
307
556
|
},
|
|
308
557
|
async beforeUpdate(event) {
|
|
309
558
|
const { data, where } = event.params || {};
|
|
310
559
|
const zones = zonesOf(strapi.getModel(event.model.uid));
|
|
311
|
-
if (!published(data) || !zones.length) return;
|
|
560
|
+
if (!published(data) || !zones.length || !await active(event.model.uid)) return;
|
|
312
561
|
const entry = zones.every((name) => Array.isArray(data[name])) ? data : { ...await strapi.entityService.findOne(event.model.uid, where?.id, { populate: zones }), ...data };
|
|
313
562
|
checkZones(strapi, event.model.uid, entry, groups);
|
|
314
563
|
},
|
|
315
564
|
async beforeUpdateMany(event) {
|
|
316
565
|
const { data, where } = event.params || {};
|
|
317
566
|
const zones = zonesOf(strapi.getModel(event.model.uid));
|
|
318
|
-
if (!published(data) || !zones.length) return;
|
|
567
|
+
if (!published(data) || !zones.length || !await active(event.model.uid)) return;
|
|
319
568
|
for (const entry of await strapi.entityService.findMany(event.model.uid, { filters: where, populate: zones })) checkZones(strapi, event.model.uid, entry, groups);
|
|
320
569
|
}
|
|
321
570
|
});
|
|
@@ -332,30 +581,65 @@ module.exports = {
|
|
|
332
581
|
blockPreview: false,
|
|
333
582
|
// Optional layout groups: OPEN component uid -> its CLOSE uid, e.g. { 'wrappers.join': 'wrappers.close' }.
|
|
334
583
|
// Empty/absent: every Dynamic Zone component is an ordinary block. See README "Layout groups".
|
|
335
|
-
groups: null
|
|
584
|
+
groups: null,
|
|
585
|
+
// Project defaults from code: same shape as the stored settings, e.g. require('./blockscene.json'). See README "Settings page".
|
|
586
|
+
settings: null,
|
|
587
|
+
// "Hide on the site": boolean attribute added to every Dynamic Zone component (a DB column); false adds nothing.
|
|
588
|
+
hiddenAttribute: "bsHidden"
|
|
336
589
|
},
|
|
337
590
|
validator: catalog
|
|
338
591
|
},
|
|
592
|
+
// Before the database schema sync, so the column exists. A name the host already uses with another type is skipped.
|
|
593
|
+
register({ strapi }) {
|
|
594
|
+
const plugin = strapi.plugin(PLUGIN);
|
|
595
|
+
const name = hiddenName(plugin.config("hiddenAttribute"));
|
|
596
|
+
if (name === null) strapi.log.warn(`[${PLUGIN}] "hiddenAttribute" config ignored: expected a name like "bsHidden" or false. Hiding blocks is off.`);
|
|
597
|
+
if (!name) return;
|
|
598
|
+
const { skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
|
|
599
|
+
if (skipped.length) strapi.log.warn(`[${PLUGIN}] "${name}" already exists with another type on ${skipped.join(", ")}; those blocks cannot be hidden.`);
|
|
600
|
+
},
|
|
339
601
|
async bootstrap({ strapi }) {
|
|
340
602
|
await strapi.admin.services.permission.actionProvider.registerMany([
|
|
341
603
|
{ section: "plugins", displayName: "Read gallery settings", uid: "settings.read", pluginName: PLUGIN },
|
|
342
604
|
{ section: "plugins", displayName: "Change gallery settings", uid: "settings.update", pluginName: PLUGIN }
|
|
343
605
|
]);
|
|
606
|
+
strapi.plugin(PLUGIN).service("settings").projectDefaults();
|
|
344
607
|
registerPublishGuard(strapi);
|
|
608
|
+
registerHiddenStrip(strapi, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")), strapi.plugin(PLUGIN));
|
|
345
609
|
},
|
|
346
|
-
services: { settings: ({ strapi }) =>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
610
|
+
services: { settings: ({ strapi }) => {
|
|
611
|
+
let project;
|
|
612
|
+
const projectDefaults = () => {
|
|
613
|
+
if (project !== void 0) return project;
|
|
614
|
+
const code = strapi.plugin(PLUGIN).config("settings");
|
|
615
|
+
project = null;
|
|
616
|
+
if (code) try {
|
|
617
|
+
project = validateSettings(code, componentUids(strapi), contentTypeUids(strapi));
|
|
618
|
+
} catch (error) {
|
|
619
|
+
strapi.log?.warn(`[${PLUGIN}] "settings" config ignored: ${error.message}. Using the built-in defaults under the saved settings.`);
|
|
354
620
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
621
|
+
return project;
|
|
622
|
+
};
|
|
623
|
+
const get = async () => mergeSaved(layer(projectDefaults(), await readSettings(strapi)), componentUids(strapi), contentTypeUids(strapi));
|
|
624
|
+
return {
|
|
625
|
+
projectDefaults,
|
|
626
|
+
get,
|
|
627
|
+
async set(value) {
|
|
628
|
+
const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi));
|
|
629
|
+
for (const [uid, entry] of Object.entries(next.components)) {
|
|
630
|
+
if (entry.mediaId && !await findMedia(strapi, entry.mediaId).catch(() => null)) fail(`Media for "${uid}" does not exist`);
|
|
631
|
+
}
|
|
632
|
+
await store(strapi).set({ key: "settings", value: overrides(next, projectDefaults()) });
|
|
633
|
+
return next;
|
|
634
|
+
},
|
|
635
|
+
// Back to the project defaults (or the built-in ones); the legacy copy goes too, or it would be migrated again.
|
|
636
|
+
async reset() {
|
|
637
|
+
await store(strapi).delete({ key: "settings" });
|
|
638
|
+
await strapi.store({ type: "plugin", name: LEGACY_PLUGIN }).delete({ key: "settings" });
|
|
639
|
+
return get();
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
} },
|
|
359
643
|
controllers: {
|
|
360
644
|
catalog: ({ strapi }) => ({
|
|
361
645
|
async find(ctx) {
|
|
@@ -367,52 +651,92 @@ module.exports = {
|
|
|
367
651
|
disabled: plugin.config("disabled"),
|
|
368
652
|
blockPreview: plugin.config("blockPreview"),
|
|
369
653
|
groups: plugin.config("groups"),
|
|
370
|
-
componentUids: componentUids(strapi)
|
|
654
|
+
componentUids: componentUids(strapi),
|
|
655
|
+
schemas: strapi.components
|
|
371
656
|
});
|
|
372
657
|
const settings = await plugin.service("settings").get();
|
|
373
658
|
const media = await resolveMedia(strapi, settings);
|
|
374
659
|
for (const [uid, entry] of Object.entries(settings.components)) {
|
|
375
|
-
base.components[uid] = {
|
|
660
|
+
base.components[uid] = {
|
|
661
|
+
...base.components[uid],
|
|
662
|
+
...media[uid],
|
|
663
|
+
template: entry.template,
|
|
664
|
+
...entry.typology && { typology: entry.typology },
|
|
665
|
+
...entry.tags && { tags: entry.tags }
|
|
666
|
+
};
|
|
376
667
|
}
|
|
668
|
+
const hidden = hiddenName(plugin.config("hiddenAttribute")) || null;
|
|
377
669
|
ctx.body = {
|
|
378
670
|
...base,
|
|
379
671
|
palette: settings.palette,
|
|
380
|
-
|
|
672
|
+
contentTypes: settings.contentTypes,
|
|
673
|
+
hiddenAttribute: hidden,
|
|
674
|
+
editor: { ...settings.editor, enabled: settings.editor.enabled && !base.disabled, ...!hidden && { hiddenBlocks: "off" } }
|
|
381
675
|
};
|
|
382
676
|
}
|
|
383
677
|
}),
|
|
384
678
|
settings: ({ strapi }) => ({
|
|
385
679
|
async find(ctx) {
|
|
386
680
|
const settings = await strapi.plugin(PLUGIN).service("settings").get();
|
|
681
|
+
const auto = catalog({ components: strapi.plugin(PLUGIN).config("components"), schemas: strapi.components }).components;
|
|
387
682
|
const components = Object.entries(strapi.components || {}).map(([uid, schema]) => ({
|
|
388
683
|
uid,
|
|
389
684
|
displayName: schema.info?.displayName || uid,
|
|
390
|
-
category: schema.category || uid.split(".")[0]
|
|
685
|
+
category: schema.category || uid.split(".")[0],
|
|
686
|
+
typology: auto[uid]?.typology
|
|
687
|
+
}));
|
|
688
|
+
const contentTypes = Object.keys(contentTypeUids(strapi)).map((uid) => ({
|
|
689
|
+
uid,
|
|
690
|
+
displayName: strapi.contentTypes[uid].info?.displayName || uid,
|
|
691
|
+
kind: strapi.contentTypes[uid].kind,
|
|
692
|
+
attributes: Object.entries(strapi.contentTypes[uid].attributes || {}).filter(([, attr]) => attr?.type !== "dynamiczone" && !attr?.private).map(([name, attr]) => ({ name, type: attr.type }))
|
|
391
693
|
}));
|
|
392
694
|
ctx.body = {
|
|
393
695
|
settings,
|
|
394
696
|
components,
|
|
697
|
+
contentTypes,
|
|
395
698
|
media: await resolveMedia(strapi, settings),
|
|
396
699
|
templates: TEMPLATES,
|
|
700
|
+
typologies: TYPOLOGIES,
|
|
397
701
|
disabled: strapi.plugin(PLUGIN).config("disabled") === true,
|
|
702
|
+
hiddenAttribute: hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")) || null,
|
|
398
703
|
blockPreviewAvailable: strapi.plugin(PLUGIN).config("blockPreview") === true,
|
|
399
|
-
defaults: DEFAULTS
|
|
704
|
+
defaults: DEFAULTS,
|
|
705
|
+
projectDefaults: strapi.plugin(PLUGIN).service("settings").projectDefaults()
|
|
400
706
|
};
|
|
401
707
|
},
|
|
402
708
|
async update(ctx) {
|
|
403
709
|
ctx.body = await strapi.plugin(PLUGIN).service("settings").set(ctx.request?.body);
|
|
710
|
+
},
|
|
711
|
+
async reset(ctx) {
|
|
712
|
+
ctx.body = await strapi.plugin(PLUGIN).service("settings").reset();
|
|
713
|
+
}
|
|
714
|
+
}),
|
|
715
|
+
prefs: ({ strapi }) => ({
|
|
716
|
+
async find(ctx) {
|
|
717
|
+
const id = ctx.state?.user?.id;
|
|
718
|
+
if (!id) return ctx.unauthorized();
|
|
719
|
+
ctx.body = mergePrefs(await store(strapi).get({ key: `prefs:${id}` }), componentUids(strapi));
|
|
720
|
+
},
|
|
721
|
+
async update(ctx) {
|
|
722
|
+
const id = ctx.state?.user?.id;
|
|
723
|
+
if (!id) return ctx.unauthorized();
|
|
724
|
+
const value = validatePrefs(ctx.request?.body, componentUids(strapi));
|
|
725
|
+
await store(strapi).set({ key: `prefs:${id}`, value });
|
|
726
|
+
ctx.body = value;
|
|
404
727
|
}
|
|
405
728
|
})
|
|
406
729
|
},
|
|
407
730
|
routes: { admin: { type: "admin", routes: [
|
|
408
731
|
{ method: "GET", path: "/catalog", handler: "catalog.find", config: { policies: ["admin::isAuthenticatedAdmin"] } },
|
|
409
|
-
...["
|
|
410
|
-
|
|
732
|
+
...["GET", "PUT"].map((method) => ({ method, path: "/me/prefs", handler: `prefs.${method === "GET" ? "find" : "update"}`, config: { policies: ["admin::isAuthenticatedAdmin"] } })),
|
|
733
|
+
...[["GET", "find", "read"], ["PUT", "update", "update"], ["DELETE", "reset", "update"]].map(([method, handler, action]) => ({
|
|
734
|
+
method,
|
|
411
735
|
path: "/settings",
|
|
412
736
|
handler: `settings.${handler}`,
|
|
413
737
|
config: { policies: ["admin::isAuthenticatedAdmin", {
|
|
414
738
|
name: "admin::hasPermissions",
|
|
415
|
-
config: { actions: [`plugin::${PLUGIN}.settings.${
|
|
739
|
+
config: { actions: [`plugin::${PLUGIN}.settings.${action}`] }
|
|
416
740
|
}] }
|
|
417
741
|
}))
|
|
418
742
|
] } }
|
package/package.json
CHANGED