@aduptive/strapi-blockscene 2.0.0-alpha.10 → 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/dist/server.cjs CHANGED
@@ -21,6 +21,10 @@ var require_groups = __commonJS({
21
21
  return Object.keys(out).length ? out : null;
22
22
  }
23
23
  var uidOf = (row) => typeof row?.__component === "string" ? row.__component : "";
24
+ var invalidComponents2 = (rows, allowed) => {
25
+ const uids = new Set(Array.isArray(allowed) ? allowed : []);
26
+ return (Array.isArray(rows) ? rows : []).flatMap((row, index) => uidOf(row) && !uids.has(uidOf(row)) ? [{ index, uid: uidOf(row) }] : []);
27
+ };
24
28
  var isOpener = (row, g) => Boolean(g) && Object.prototype.hasOwnProperty.call(g, uidOf(row));
25
29
  var isClose = (row, g) => Boolean(g) && Object.values(g).includes(uidOf(row));
26
30
  var blockKey = (row) => row && row.id != null ? `${row.__component}#${row.id}` : row && row.__temp_key__ != null ? String(row.__temp_key__) : null;
@@ -127,6 +131,7 @@ var require_groups = __commonJS({
127
131
  module2.exports = {
128
132
  safeGroups: safeGroups2,
129
133
  validateGroups: validateGroups2,
134
+ invalidComponents: invalidComponents2,
130
135
  groupRange,
131
136
  groupRows,
132
137
  topLevelRanges,
@@ -182,7 +187,7 @@ var require_settings = __commonJS({
182
187
  // Edit view labels that are still the raw attribute name read as "Mobile columns count" (see README "Field labels").
183
188
  friendlyLabels: true,
184
189
  // Page preview pane (Strapi 5): which toolbar controls show, in order, and the width menu's entries.
185
- previewToolbar: ["modes", "history", "devices", "status", "actions"],
190
+ previewToolbar: ["modes", "history", "versions", "devices", "status", "actions"],
186
191
  previewDevices: ["fit", "mobile", "tablet", "desktop"]
187
192
  },
188
193
  // Per content type (only the ones with a Dynamic Zone): { enabled: false } turns the plugin off there;
@@ -197,7 +202,7 @@ var require_settings = __commonJS({
197
202
  var SIDEBAR_POSITIONS = ["left", "right", "bottom"];
198
203
  var OPENS = ["modal", "drawer"];
199
204
  var LABEL = /^[^<>]{1,40}$/;
200
- var TOOLBAR = ["modes", "history", "devices", "status", "actions"];
205
+ var TOOLBAR = ["modes", "history", "versions", "devices", "status", "actions"];
201
206
  var DEVICE_NAMES = ["fit", "mobile", "tablet", "desktop"];
202
207
  var previewToolbar = (value) => Array.isArray(value) && value.every((id) => TOOLBAR.includes(id)) && new Set(value).size === value.length;
203
208
  var DEVICE_LABEL = /^[^<>]{1,24}$/;
@@ -619,6 +624,102 @@ var require_settings = __commonJS({
619
624
  }
620
625
  return out;
621
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
+ }
622
723
  var PREFS = { starred: 200, recent: 20 };
623
724
  function validatePrefs2(input, componentUids2) {
624
725
  if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Preferences must be an object");
@@ -631,7 +732,7 @@ var require_settings = __commonJS({
631
732
  return out;
632
733
  }
633
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) : []]));
634
- 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 };
635
736
  }
636
737
  });
637
738
 
@@ -821,8 +922,8 @@ var require_diff = __commonJS({
821
922
  }
822
923
  function summarize(before, after, schema) {
823
924
  const zones = Object.entries(schema?.attributes || {}).filter(([, attr]) => attr?.type === "dynamiczone").map(([name]) => name);
824
- const blocks = { added: 0, removed: 0, changed: 0 };
825
- 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 } };
826
927
  const names = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after || {})])];
827
928
  const fields = names.filter((name) => !zones.includes(name) && !same(before[name], after?.[name])).sort();
828
929
  for (const name of zones) for (const { op } of diffRows(before[name], after?.[name])) if (op !== "same") blocks[op]++;
@@ -1293,7 +1394,6 @@ var require_history = __commonJS({
1293
1394
  var CRON2 = "blocksceneHistoryPurge";
1294
1395
  var BATCH = 500;
1295
1396
  var DAY = 24 * 60 * 60 * 1e3;
1296
- var LIST_LIMIT = 100;
1297
1397
  var ACTORS_LIMIT = 500;
1298
1398
  var SEARCH_LIMIT = 200;
1299
1399
  var eventContentType2 = { schema: {
@@ -1436,14 +1536,23 @@ var require_history = __commonJS({
1436
1536
  return name && name !== "id" && schemaOf(uid)?.attributes?.[name] && !["relation", "media", "component", "dynamiczone", "password", "json", "blocks"].includes(schemaOf(uid).attributes[name].type) ? name : null;
1437
1537
  },
1438
1538
  // Latest events of one document and locale, newest first; `stored`: its snapshot (or an identical one) can be loaded.
1439
- async list({ uid, documentId, locale }) {
1440
- const where = { contentType: uid, relatedDocumentId: documentId, locale: locale || null };
1441
- const [list, kept] = await Promise.all([
1442
- events().findMany({ select: ["id", "action", "actor", "actorName", "at", "summary", "hash", "size"], where, orderBy: [{ at: "desc" }, { id: "desc" }], limit: LIST_LIMIT }),
1443
- events().findMany({ select: ["hash"], where: { ...where, size: { $gt: 0 } } })
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" }] })
1444
1547
  ]);
1445
1548
  const hashes = new Set(kept.map((row) => row.hash));
1446
- return list.map(({ hash, size, ...event }) => ({ ...event, stored: Boolean(hash && hashes.has(hash)) }));
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
+ };
1447
1556
  },
1448
1557
  // One event with its snapshot (from an identical event when this one was deduplicated) and what loading it needs:
1449
1558
  // the media files and relation targets that still exist (relation labels from the Content Manager's main field).
@@ -1655,10 +1764,13 @@ var require_history = __commonJS({
1655
1764
  async list(ctx) {
1656
1765
  const { uid, documentId } = ctx.params || {};
1657
1766
  const locale = ctx.query?.locale || "";
1658
- if (!String(uid).startsWith("api::") || !strapi.contentTypes?.[uid] || !DOCUMENT_ID.test(documentId || "") || locale && !LOCALE.test(locale)) return ctx.badRequest("Invalid content type, document or locale");
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");
1659
1771
  if (!canRead(strapi, ctx, uid)) return ctx.forbidden();
1660
1772
  const history = strapi.plugin(PLUGIN2).service("history");
1661
- ctx.body = { results: await history.list({ uid, documentId, locale: (await history.scope(uid, locale || void 0))[0] }) };
1773
+ ctx.body = await history.list({ uid, documentId, locale: (await history.scope(uid, locale || void 0))[0], actor, page, pageSize });
1662
1774
  },
1663
1775
  async find(ctx) {
1664
1776
  const id = Number(ctx.params?.id);
@@ -1685,8 +1797,8 @@ var require_history = __commonJS({
1685
1797
  });
1686
1798
 
1687
1799
  // server/index.js
1688
- var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1689
- var { safeGroups, validateGroups, layoutFields } = require_groups();
1800
+ var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, validateKits, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1801
+ var { safeGroups, validateGroups, invalidComponents, layoutFields } = require_groups();
1690
1802
  var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
1691
1803
  var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
1692
1804
  var { trashContentType, trashService, trashController } = require_trash();
@@ -1719,6 +1831,18 @@ var contentTypeUids = (strapi) => Object.fromEntries(Object.entries(strapi.conte
1719
1831
  var apiUids = (strapi) => Object.keys(strapi.contentTypes || {}).filter((uid) => uid.startsWith("api::"));
1720
1832
  var zonesOf = (schema) => Object.entries(schema?.attributes || {}).filter(([, attr]) => attr?.type === "dynamiczone").map(([name]) => name);
1721
1833
  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})`}`;
1834
+ var componentLabel = (error) => `row ${error.index + 1}: component "${error.uid}" is not allowed in this Dynamic Zone`;
1835
+ function checkComponents(strapi, uid, entry) {
1836
+ const schema = strapi.getModel(uid);
1837
+ for (const name of zonesOf(schema)) {
1838
+ if (!Array.isArray(entry?.[name])) continue;
1839
+ const errors = invalidComponents(entry[name], schema.attributes[name].components);
1840
+ if (errors.length) fail(
1841
+ `Block components in "${name}" are not allowed by its schema (${errors.map(componentLabel).join("; ")}).`,
1842
+ { plugin: PLUGIN, field: name, errors: errors.map((error) => ({ path: [name, error.index], message: componentLabel(error), name: "ValidationError", code: "componentNotAllowed", uid: error.uid })) }
1843
+ );
1844
+ }
1845
+ }
1722
1846
  function checkZones(strapi, uid, entry, groups) {
1723
1847
  for (const name of zonesOf(strapi.getModel(uid))) {
1724
1848
  const errors = validateGroups(Array.isArray(entry?.[name]) ? entry[name] : [], groups);
@@ -1728,11 +1852,10 @@ function checkZones(strapi, uid, entry, groups) {
1728
1852
  );
1729
1853
  }
1730
1854
  }
1731
- function registerPublishGuard(strapi) {
1855
+ function registerZoneGuards(strapi) {
1732
1856
  const plugin = strapi.plugin(PLUGIN);
1733
1857
  const groups = safeGroups(plugin.config("groups"), componentUids(strapi));
1734
1858
  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.`);
1735
- if (!groups) return;
1736
1859
  const active = async (uid) => {
1737
1860
  if (plugin.config("disabled") === true) return false;
1738
1861
  const settings = await plugin.service("settings").get();
@@ -1749,12 +1872,14 @@ function registerPublishGuard(strapi) {
1749
1872
  strapi.documents.use(async (ctx, next) => {
1750
1873
  const { action, params = {} } = ctx;
1751
1874
  const publishing = action === "publish" || (action === "create" || action === "update") && params.status === "published";
1752
- if (!publishing || !ctx.uid) return next();
1875
+ if (!ctx.uid || !["create", "update", "publish"].includes(action)) return next();
1753
1876
  const model = strapi.getModel(ctx.uid);
1754
1877
  const zones = zonesOf(model);
1755
1878
  if (!zones.length || !await active(ctx.uid)) return next();
1879
+ if (action === "create" || action === "update") checkComponents(strapi, ctx.uid, params.data || {});
1880
+ if (!publishing) return next();
1756
1881
  if (action === "create") {
1757
- checkZones(strapi, ctx.uid, params.data || {}, groups);
1882
+ if (groups) checkZones(strapi, ctx.uid, params.data || {}, groups);
1758
1883
  return next();
1759
1884
  }
1760
1885
  const populate = Object.fromEntries(zones.map((name) => [name, true]));
@@ -1769,7 +1894,8 @@ function registerPublishGuard(strapi) {
1769
1894
  }
1770
1895
  for (const draft of drafts) {
1771
1896
  const entry = action === "update" ? { ...draft || {}, ...Object.fromEntries(zones.filter((name) => Array.isArray(params.data?.[name])).map((name) => [name, params.data[name]])) } : draft;
1772
- checkZones(strapi, ctx.uid, entry, groups);
1897
+ checkComponents(strapi, ctx.uid, entry);
1898
+ if (groups) checkZones(strapi, ctx.uid, entry, groups);
1773
1899
  }
1774
1900
  return next();
1775
1901
  });
@@ -1778,20 +1904,31 @@ function registerPublishGuard(strapi) {
1778
1904
  const published = (data) => Boolean(data && data.publishedAt);
1779
1905
  strapi.db.lifecycles.subscribe({
1780
1906
  async beforeCreate(event) {
1781
- if (published(event.params?.data) && await active(event.model.uid)) checkZones(strapi, event.model.uid, event.params.data, groups);
1907
+ const data = event.params?.data, uid = event.model.uid;
1908
+ if (!zonesOf(strapi.getModel(uid)).length || !await active(uid)) return;
1909
+ checkComponents(strapi, uid, data || {});
1910
+ if (groups && published(data)) checkZones(strapi, uid, data, groups);
1782
1911
  },
1783
1912
  async beforeUpdate(event) {
1784
1913
  const { data, where } = event.params || {};
1785
1914
  const zones = zonesOf(strapi.getModel(event.model.uid));
1786
- if (!published(data) || !zones.length || !await active(event.model.uid)) return;
1915
+ if (!zones.length || !await active(event.model.uid)) return;
1916
+ checkComponents(strapi, event.model.uid, data || {});
1917
+ if (!published(data)) return;
1787
1918
  const entry = zones.every((name) => Array.isArray(data[name])) ? data : { ...await strapi.entityService.findOne(event.model.uid, where?.id, { populate: zones }), ...data };
1788
- checkZones(strapi, event.model.uid, entry, groups);
1919
+ checkComponents(strapi, event.model.uid, entry);
1920
+ if (groups) checkZones(strapi, event.model.uid, entry, groups);
1789
1921
  },
1790
1922
  async beforeUpdateMany(event) {
1791
1923
  const { data, where } = event.params || {};
1792
1924
  const zones = zonesOf(strapi.getModel(event.model.uid));
1793
- if (!published(data) || !zones.length || !await active(event.model.uid)) return;
1794
- for (const entry of await strapi.entityService.findMany(event.model.uid, { filters: where, populate: zones })) checkZones(strapi, event.model.uid, entry, groups);
1925
+ if (!zones.length || !await active(event.model.uid)) return;
1926
+ checkComponents(strapi, event.model.uid, data || {});
1927
+ if (!published(data)) return;
1928
+ for (const entry of await strapi.entityService.findMany(event.model.uid, { filters: where, populate: zones })) {
1929
+ checkComponents(strapi, event.model.uid, entry);
1930
+ if (groups) checkZones(strapi, event.model.uid, entry, groups);
1931
+ }
1795
1932
  }
1796
1933
  });
1797
1934
  }
@@ -1812,6 +1949,8 @@ module.exports = {
1812
1949
  settings: null,
1813
1950
  // Edit view field texts from code (never stored): { uid: { attribute: { label, description, placeholder } } }. See README "Field labels".
1814
1951
  fields: null,
1952
+ // Initial block sets per content type, shown only on a new empty entry. See README "Starter kits".
1953
+ kits: null,
1815
1954
  // Other languages for the schema metadata (pluginOptions.blockscene, info.description), one flat map per locale:
1816
1955
  // { 'pt-BR': require('./blockscene/pt-BR.json') }. See README "Describing blocks and fields in the schema".
1817
1956
  translations: null,
@@ -1846,7 +1985,8 @@ module.exports = {
1846
1985
  strapi.plugin(PLUGIN).service("settings").projectDefaults();
1847
1986
  strapi.plugin(PLUGIN).service("settings").fields();
1848
1987
  strapi.plugin(PLUGIN).service("settings").variants();
1849
- registerPublishGuard(strapi);
1988
+ strapi.plugin(PLUGIN).service("settings").kits();
1989
+ registerZoneGuards(strapi);
1850
1990
  registerHiddenStrip(strapi, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")), strapi.plugin(PLUGIN));
1851
1991
  if (withHidden.length) hideFromBuilder(typeof strapi.get === "function" ? strapi.get("services") : strapi.container.get("services"), withHidden, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")));
1852
1992
  if (STRAPI5) {
@@ -1904,6 +2044,14 @@ module.exports = {
1904
2044
  for (const note of notes) strapi.log?.warn(`[${PLUGIN}] components variants: ${note}.`);
1905
2045
  return variants;
1906
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
+ };
1907
2055
  const get = async () => mergeSaved(layer(projectDefaults(), await readSettings(strapi)), componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1908
2056
  return {
1909
2057
  projectDefaults,
@@ -1911,6 +2059,7 @@ module.exports = {
1911
2059
  fields: fieldTexts,
1912
2060
  schema: schemaMeta,
1913
2061
  variants: insertVariants,
2062
+ kits: starterKits,
1914
2063
  async set(value) {
1915
2064
  const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1916
2065
  for (const [uid, entry] of Object.entries(next.components)) {
@@ -1961,6 +2110,7 @@ module.exports = {
1961
2110
  ctx.body = {
1962
2111
  ...base,
1963
2112
  layouts,
2113
+ kits: plugin.service("settings").kits(),
1964
2114
  palette: settings.palette,
1965
2115
  contentTypes: settings.contentTypes,
1966
2116
  hiddenAttribute: hidden,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aduptive/strapi-blockscene",
3
- "version": "2.0.0-alpha.10",
3
+ "version": "2.0.0-alpha.12",
4
4
  "description": "Visual component gallery for native Strapi dynamic zones.",
5
5
  "license": "MIT",
6
6
  "author": "Andrea Scarpello <aduptive@gmail.com>",