@aduptive/strapi-blockscene 2.0.0-alpha.11 → 2.0.0-alpha.12
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 +87 -10
- package/dist/admin.cjs +1081 -545
- package/dist/admin.mjs +1078 -542
- package/dist/server.cjs +135 -15
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -187,7 +187,7 @@ var require_settings = __commonJS({
|
|
|
187
187
|
// Edit view labels that are still the raw attribute name read as "Mobile columns count" (see README "Field labels").
|
|
188
188
|
friendlyLabels: true,
|
|
189
189
|
// Page preview pane (Strapi 5): which toolbar controls show, in order, and the width menu's entries.
|
|
190
|
-
previewToolbar: ["modes", "history", "devices", "status", "actions"],
|
|
190
|
+
previewToolbar: ["modes", "history", "versions", "devices", "status", "actions"],
|
|
191
191
|
previewDevices: ["fit", "mobile", "tablet", "desktop"]
|
|
192
192
|
},
|
|
193
193
|
// Per content type (only the ones with a Dynamic Zone): { enabled: false } turns the plugin off there;
|
|
@@ -202,7 +202,7 @@ var require_settings = __commonJS({
|
|
|
202
202
|
var SIDEBAR_POSITIONS = ["left", "right", "bottom"];
|
|
203
203
|
var OPENS = ["modal", "drawer"];
|
|
204
204
|
var LABEL = /^[^<>]{1,40}$/;
|
|
205
|
-
var TOOLBAR = ["modes", "history", "devices", "status", "actions"];
|
|
205
|
+
var TOOLBAR = ["modes", "history", "versions", "devices", "status", "actions"];
|
|
206
206
|
var DEVICE_NAMES = ["fit", "mobile", "tablet", "desktop"];
|
|
207
207
|
var previewToolbar = (value) => Array.isArray(value) && value.every((id) => TOOLBAR.includes(id)) && new Set(value).size === value.length;
|
|
208
208
|
var DEVICE_LABEL = /^[^<>]{1,24}$/;
|
|
@@ -624,6 +624,102 @@ var require_settings = __commonJS({
|
|
|
624
624
|
}
|
|
625
625
|
return out;
|
|
626
626
|
}
|
|
627
|
+
function requiredProblems(schema, values, schemas, path, media, problems) {
|
|
628
|
+
for (const [field, attr] of Object.entries(schema?.attributes || {})) {
|
|
629
|
+
const at = `${path}.${field}`;
|
|
630
|
+
const supplied = Object.hasOwn(values, field);
|
|
631
|
+
const value = supplied ? values[field] : attr.default;
|
|
632
|
+
if (attr.type === "media" || attr.type === "relation") {
|
|
633
|
+
if (attr.required && !supplied) media.push(at);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (attr.type === "component") {
|
|
637
|
+
const nested = schemas[attr.component];
|
|
638
|
+
if (!nested) {
|
|
639
|
+
problems.push(`${at}: unknown component "${attr.component}"`);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (attr.repeatable) {
|
|
643
|
+
const minimum = Math.max(attr.min ?? 0, attr.required ? 1 : 0);
|
|
644
|
+
const items = supplied ? value : Array.from({ length: minimum }, () => ({}));
|
|
645
|
+
if (!Array.isArray(items) || items.length < minimum) problems.push(`${at} requires at least ${minimum} item${minimum === 1 ? "" : "s"}`);
|
|
646
|
+
else items.forEach((item, index) => requiredProblems(nested, item, schemas, `${at}[${index}]`, media, problems));
|
|
647
|
+
} else if (supplied && value === null) {
|
|
648
|
+
if (attr.required) problems.push(`${at} is required`);
|
|
649
|
+
} else if (supplied || attr.required) requiredProblems(nested, supplied ? value : {}, schemas, at, media, problems);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (attr.required && (value === void 0 || value === null || value === "")) problems.push(`${at} is required`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function validateKits2(input, contentTypes = {}, schemas = {}, groups = null, notes = []) {
|
|
656
|
+
if (input === null || input === void 0) return {};
|
|
657
|
+
if (!plain(input) || JSON.stringify(input).length > 256 * 1024) {
|
|
658
|
+
notes.push("expected an object up to 256 KB");
|
|
659
|
+
return {};
|
|
660
|
+
}
|
|
661
|
+
const out = {};
|
|
662
|
+
for (const [uid, kits] of Object.entries(input)) {
|
|
663
|
+
const type = contentTypes[uid];
|
|
664
|
+
if (!type || !uid.startsWith("api::")) {
|
|
665
|
+
notes.push(`${uid}: unknown content type`);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (!Array.isArray(kits) || kits.length > 12) {
|
|
669
|
+
notes.push(`${uid}: kits must be a list of at most 12`);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const ids = /* @__PURE__ */ new Set();
|
|
673
|
+
for (const kit of kits) {
|
|
674
|
+
const name = `${uid} kit "${kit?.id ?? "?"}"`;
|
|
675
|
+
try {
|
|
676
|
+
if (!plain(kit) || Object.keys(kit).some((key) => !["id", "label", "locales", "zones"].includes(key))) throw new Error("expected { id, label, locales?, zones }");
|
|
677
|
+
if (!text(kit.id) || !VARIANT.id.test(kit.id) || ids.has(kit.id)) throw new Error('id must be 1 to 40 lowercase letters, digits, "-" or "_", unique per content type');
|
|
678
|
+
if (!fieldText(kit.label, 60)) throw new Error('label must be a string of 1 to 60 characters, or { "<locale>": string }');
|
|
679
|
+
if (kit.locales !== void 0 && (!Array.isArray(kit.locales) || !kit.locales.length || kit.locales.length > 20 || kit.locales.some((locale) => !text(locale) || !LOCALE.test(locale)))) throw new Error("locales must be 1 to 20 locale codes");
|
|
680
|
+
if (!plain(kit.zones) || !Object.keys(kit.zones).length) throw new Error("zones must be a non-empty object");
|
|
681
|
+
const zones = {};
|
|
682
|
+
for (const [zoneName, rows] of Object.entries(kit.zones)) {
|
|
683
|
+
const zone = type.attributes?.[zoneName];
|
|
684
|
+
if (zone?.type !== "dynamiczone") throw new Error(`unknown Dynamic Zone "${zoneName}"`);
|
|
685
|
+
if (!Array.isArray(rows) || !rows.length || rows.length > 50) throw new Error(`${zoneName} must contain 1 to 50 rows`);
|
|
686
|
+
const clean = [];
|
|
687
|
+
for (let index = 0; index < rows.length; index++) {
|
|
688
|
+
const row = rows[index];
|
|
689
|
+
if (!plain(row) || !text(row.__component)) throw new Error(`${zoneName}[${index}] must have __component`);
|
|
690
|
+
const component = row.__component;
|
|
691
|
+
if (!zone.components?.includes(component) || !schemas[component]) throw new Error(`${zoneName}[${index}]: component "${component}" is not allowed`);
|
|
692
|
+
const skipped = [];
|
|
693
|
+
const values = variantValues(row, schemas[component], schemas, `${zoneName}[${index}]`, skipped);
|
|
694
|
+
if (skipped.length) notes.push(`${name}: media and relation values are not inserted (${skipped.slice(0, 5).join(", ")}${skipped.length > 5 ? ", ..." : ""})`);
|
|
695
|
+
const media = [], required = [];
|
|
696
|
+
requiredProblems(schemas[component], values, schemas, `${zoneName}[${index}]`, media, required);
|
|
697
|
+
if (required.length) throw new Error(required.slice(0, 5).join("; "));
|
|
698
|
+
if (media.length) notes.push(`${name}: required media or relations must be completed before publishing (${media.slice(0, 5).join(", ")}${media.length > 5 ? ", ..." : ""})`);
|
|
699
|
+
clean.push({ __component: component, values });
|
|
700
|
+
const close = groups?.[component];
|
|
701
|
+
if (close) {
|
|
702
|
+
if (!zone.components?.includes(close) || !schemas[close]) throw new Error(`${zoneName}[${index}]: close component "${close}" is not allowed`);
|
|
703
|
+
clean.push({ __component: close, values: {} });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (clean.length > (zone.max ?? Infinity)) throw new Error(`${zoneName} exceeds its maximum of ${zone.max}`);
|
|
707
|
+
zones[zoneName] = clean;
|
|
708
|
+
}
|
|
709
|
+
for (const [zoneName, zone] of Object.entries(type.attributes || {})) {
|
|
710
|
+
if (zone?.type !== "dynamiczone") continue;
|
|
711
|
+
const minimum = Math.max(zone.min ?? 0, zone.required ? 1 : 0);
|
|
712
|
+
if ((zones[zoneName]?.length || 0) < minimum) throw new Error(`${zoneName} requires at least ${minimum} row${minimum === 1 ? "" : "s"}`);
|
|
713
|
+
}
|
|
714
|
+
ids.add(kit.id);
|
|
715
|
+
(out[uid] ||= []).push({ id: kit.id, label: structuredClone(kit.label), ...kit.locales && { locales: [...new Set(kit.locales)] }, zones });
|
|
716
|
+
} catch (error) {
|
|
717
|
+
notes.push(`${name} ignored: ${error.message}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return out;
|
|
722
|
+
}
|
|
627
723
|
var PREFS = { starred: 200, recent: 20 };
|
|
628
724
|
function validatePrefs2(input, componentUids2) {
|
|
629
725
|
if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Preferences must be an object");
|
|
@@ -636,7 +732,7 @@ var require_settings = __commonJS({
|
|
|
636
732
|
return out;
|
|
637
733
|
}
|
|
638
734
|
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) : []]));
|
|
639
|
-
module2.exports = { PLUGIN: PLUGIN2, HIDDEN_MODES, TEMPLATES: TEMPLATES2, TYPOLOGIES: TYPOLOGIES2, guessTypology, facetsOf, validatePrefs: validatePrefs2, mergePrefs: mergePrefs2, ICONS, DEFAULTS: DEFAULTS2, catalog: catalog2, validateSettings: validateSettings2, validateFields: validateFields2, validateVariants: validateVariants2, schemaMetadata: schemaMetadata2, mergeSaved: mergeSaved2, layer: layer2, overrides: overrides2, safeUrl: safeUrl2, fail: fail2 };
|
|
735
|
+
module2.exports = { PLUGIN: PLUGIN2, HIDDEN_MODES, TEMPLATES: TEMPLATES2, TYPOLOGIES: TYPOLOGIES2, guessTypology, facetsOf, validatePrefs: validatePrefs2, mergePrefs: mergePrefs2, ICONS, DEFAULTS: DEFAULTS2, catalog: catalog2, validateSettings: validateSettings2, validateFields: validateFields2, validateVariants: validateVariants2, validateKits: validateKits2, schemaMetadata: schemaMetadata2, mergeSaved: mergeSaved2, layer: layer2, overrides: overrides2, safeUrl: safeUrl2, fail: fail2 };
|
|
640
736
|
}
|
|
641
737
|
});
|
|
642
738
|
|
|
@@ -826,8 +922,8 @@ var require_diff = __commonJS({
|
|
|
826
922
|
}
|
|
827
923
|
function summarize(before, after, schema) {
|
|
828
924
|
const zones = Object.entries(schema?.attributes || {}).filter(([, attr]) => attr?.type === "dynamiczone").map(([name]) => name);
|
|
829
|
-
const blocks = { added: 0, removed: 0, changed: 0 };
|
|
830
|
-
if (!before) return { initial: true, fields: [], blocks };
|
|
925
|
+
const blocks = { total: zones.reduce((n, name) => n + (Array.isArray(after?.[name]) ? after[name].length : 0), 0), added: 0, removed: 0, changed: 0 };
|
|
926
|
+
if (!before) return { initial: true, fields: [], blocks: { ...blocks, added: blocks.total } };
|
|
831
927
|
const names = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after || {})])];
|
|
832
928
|
const fields = names.filter((name) => !zones.includes(name) && !same(before[name], after?.[name])).sort();
|
|
833
929
|
for (const name of zones) for (const { op } of diffRows(before[name], after?.[name])) if (op !== "same") blocks[op]++;
|
|
@@ -1298,7 +1394,6 @@ var require_history = __commonJS({
|
|
|
1298
1394
|
var CRON2 = "blocksceneHistoryPurge";
|
|
1299
1395
|
var BATCH = 500;
|
|
1300
1396
|
var DAY = 24 * 60 * 60 * 1e3;
|
|
1301
|
-
var LIST_LIMIT = 100;
|
|
1302
1397
|
var ACTORS_LIMIT = 500;
|
|
1303
1398
|
var SEARCH_LIMIT = 200;
|
|
1304
1399
|
var eventContentType2 = { schema: {
|
|
@@ -1441,14 +1536,23 @@ var require_history = __commonJS({
|
|
|
1441
1536
|
return name && name !== "id" && schemaOf(uid)?.attributes?.[name] && !["relation", "media", "component", "dynamiczone", "password", "json", "blocks"].includes(schemaOf(uid).attributes[name].type) ? name : null;
|
|
1442
1537
|
},
|
|
1443
1538
|
// Latest events of one document and locale, newest first; `stored`: its snapshot (or an identical one) can be loaded.
|
|
1444
|
-
async list({ uid, documentId, locale }) {
|
|
1445
|
-
const
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
events().findMany({ select: ["hash"], where: {
|
|
1539
|
+
async list({ uid, documentId, locale, actor = "", page = 1, pageSize = 25 }) {
|
|
1540
|
+
const scope2 = { contentType: uid, relatedDocumentId: documentId, locale: locale || null };
|
|
1541
|
+
const where = { ...scope2, ...actor && { actor } };
|
|
1542
|
+
const [list, kept, total, people] = await Promise.all([
|
|
1543
|
+
events().findMany({ select: ["id", "action", "actor", "actorName", "at", "summary", "hash", "size"], where, orderBy: [{ at: "desc" }, { id: "desc" }], offset: (page - 1) * pageSize, limit: pageSize }),
|
|
1544
|
+
events().findMany({ select: ["hash"], where: { ...scope2, size: { $gt: 0 } } }),
|
|
1545
|
+
events().count({ where }),
|
|
1546
|
+
events().findMany({ select: ["actor", "actorName"], where: scope2, orderBy: [{ at: "desc" }, { id: "desc" }] })
|
|
1449
1547
|
]);
|
|
1450
1548
|
const hashes = new Set(kept.map((row) => row.hash));
|
|
1451
|
-
|
|
1549
|
+
const actors2 = [...new Map(people.slice().reverse().map(({ actor: actor2, actorName }) => [actor2, { actor: actor2, actorName }])).values()];
|
|
1550
|
+
return {
|
|
1551
|
+
results: list.map(({ hash, size, ...event }) => ({ ...event, stored: Boolean(hash && hashes.has(hash)) })),
|
|
1552
|
+
pagination: { page, pageSize, total, pageCount: Math.ceil(total / pageSize) },
|
|
1553
|
+
actors: actors2,
|
|
1554
|
+
locale: locale || null
|
|
1555
|
+
};
|
|
1452
1556
|
},
|
|
1453
1557
|
// One event with its snapshot (from an identical event when this one was deduplicated) and what loading it needs:
|
|
1454
1558
|
// the media files and relation targets that still exist (relation labels from the Content Manager's main field).
|
|
@@ -1660,10 +1764,13 @@ var require_history = __commonJS({
|
|
|
1660
1764
|
async list(ctx) {
|
|
1661
1765
|
const { uid, documentId } = ctx.params || {};
|
|
1662
1766
|
const locale = ctx.query?.locale || "";
|
|
1663
|
-
|
|
1767
|
+
const actor = ctx.query?.actor || "";
|
|
1768
|
+
const page = int(ctx.query?.page, 1, 1e6), pageSize = int(ctx.query?.pageSize, 25, 100);
|
|
1769
|
+
if (typeof actor !== "string" || actor && !ACTOR.test(actor) || [page, pageSize].some(Number.isNaN)) return ctx.badRequest("Invalid history filters");
|
|
1770
|
+
if (!String(uid).startsWith("api::") || !strapi.contentTypes?.[uid] || !DOCUMENT_ID.test(documentId || "") || typeof locale !== "string" || locale && !LOCALE.test(locale)) return ctx.badRequest("Invalid content type, document or locale");
|
|
1664
1771
|
if (!canRead(strapi, ctx, uid)) return ctx.forbidden();
|
|
1665
1772
|
const history = strapi.plugin(PLUGIN2).service("history");
|
|
1666
|
-
ctx.body =
|
|
1773
|
+
ctx.body = await history.list({ uid, documentId, locale: (await history.scope(uid, locale || void 0))[0], actor, page, pageSize });
|
|
1667
1774
|
},
|
|
1668
1775
|
async find(ctx) {
|
|
1669
1776
|
const id = Number(ctx.params?.id);
|
|
@@ -1690,7 +1797,7 @@ var require_history = __commonJS({
|
|
|
1690
1797
|
});
|
|
1691
1798
|
|
|
1692
1799
|
// server/index.js
|
|
1693
|
-
var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
|
|
1800
|
+
var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, validateKits, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
|
|
1694
1801
|
var { safeGroups, validateGroups, invalidComponents, layoutFields } = require_groups();
|
|
1695
1802
|
var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
|
|
1696
1803
|
var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
|
|
@@ -1842,6 +1949,8 @@ module.exports = {
|
|
|
1842
1949
|
settings: null,
|
|
1843
1950
|
// Edit view field texts from code (never stored): { uid: { attribute: { label, description, placeholder } } }. See README "Field labels".
|
|
1844
1951
|
fields: null,
|
|
1952
|
+
// Initial block sets per content type, shown only on a new empty entry. See README "Starter kits".
|
|
1953
|
+
kits: null,
|
|
1845
1954
|
// Other languages for the schema metadata (pluginOptions.blockscene, info.description), one flat map per locale:
|
|
1846
1955
|
// { 'pt-BR': require('./blockscene/pt-BR.json') }. See README "Describing blocks and fields in the schema".
|
|
1847
1956
|
translations: null,
|
|
@@ -1876,6 +1985,7 @@ module.exports = {
|
|
|
1876
1985
|
strapi.plugin(PLUGIN).service("settings").projectDefaults();
|
|
1877
1986
|
strapi.plugin(PLUGIN).service("settings").fields();
|
|
1878
1987
|
strapi.plugin(PLUGIN).service("settings").variants();
|
|
1988
|
+
strapi.plugin(PLUGIN).service("settings").kits();
|
|
1879
1989
|
registerZoneGuards(strapi);
|
|
1880
1990
|
registerHiddenStrip(strapi, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")), strapi.plugin(PLUGIN));
|
|
1881
1991
|
if (withHidden.length) hideFromBuilder(typeof strapi.get === "function" ? strapi.get("services") : strapi.container.get("services"), withHidden, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")));
|
|
@@ -1934,6 +2044,14 @@ module.exports = {
|
|
|
1934
2044
|
for (const note of notes) strapi.log?.warn(`[${PLUGIN}] components variants: ${note}.`);
|
|
1935
2045
|
return variants;
|
|
1936
2046
|
};
|
|
2047
|
+
let kits;
|
|
2048
|
+
const starterKits = () => {
|
|
2049
|
+
if (kits !== void 0) return kits;
|
|
2050
|
+
const notes = [];
|
|
2051
|
+
kits = validateKits(strapi.plugin(PLUGIN).config("kits"), strapi.contentTypes || {}, strapi.components || {}, safeGroups(strapi.plugin(PLUGIN).config("groups"), componentUids(strapi)), notes);
|
|
2052
|
+
for (const note of notes) strapi.log?.warn(`[${PLUGIN}] starter kits: ${note}.`);
|
|
2053
|
+
return kits;
|
|
2054
|
+
};
|
|
1937
2055
|
const get = async () => mergeSaved(layer(projectDefaults(), await readSettings(strapi)), componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
|
|
1938
2056
|
return {
|
|
1939
2057
|
projectDefaults,
|
|
@@ -1941,6 +2059,7 @@ module.exports = {
|
|
|
1941
2059
|
fields: fieldTexts,
|
|
1942
2060
|
schema: schemaMeta,
|
|
1943
2061
|
variants: insertVariants,
|
|
2062
|
+
kits: starterKits,
|
|
1944
2063
|
async set(value) {
|
|
1945
2064
|
const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
|
|
1946
2065
|
for (const [uid, entry] of Object.entries(next.components)) {
|
|
@@ -1991,6 +2110,7 @@ module.exports = {
|
|
|
1991
2110
|
ctx.body = {
|
|
1992
2111
|
...base,
|
|
1993
2112
|
layouts,
|
|
2113
|
+
kits: plugin.service("settings").kits(),
|
|
1994
2114
|
palette: settings.palette,
|
|
1995
2115
|
contentTypes: settings.contentTypes,
|
|
1996
2116
|
hiddenAttribute: hidden,
|
package/package.json
CHANGED