@aduptive/strapi-blockscene 2.0.0-alpha.11 → 2.0.0-alpha.13

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
@@ -108,8 +108,17 @@ var require_groups = __commonJS({
108
108
  const start = rows.findIndex((row) => blockKey(row) === String(key));
109
109
  if (start < 0 || !isOpener(rows[start], g)) return null;
110
110
  const [, end] = groupRange(rows, start, g);
111
+ if (end <= start || uidOf(rows[end]) !== g[uidOf(rows[start])]) return null;
111
112
  return [...rows.slice(0, start), ...rows.slice(end + 1)];
112
113
  }
114
+ function unwrapGroup(rows, key, g) {
115
+ if (!g) return null;
116
+ const start = rows.findIndex((row) => blockKey(row) === String(key));
117
+ if (start < 0 || !isOpener(rows[start], g)) return null;
118
+ const [, end] = groupRange(rows, start, g);
119
+ if (end <= start || uidOf(rows[end]) !== g[uidOf(rows[start])]) return null;
120
+ return [...rows.slice(0, start), ...rows.slice(start + 1, end), ...rows.slice(end + 1)];
121
+ }
113
122
  var MAX_COLUMNS = 12;
114
123
  var parseColumns = (value) => {
115
124
  const n = typeof value === "number" ? value : typeof value === "string" && /^\s*\d{1,3}\s*$/.test(value) ? Number(value) : NaN;
@@ -137,6 +146,7 @@ var require_groups = __commonJS({
137
146
  topLevelRanges,
138
147
  moveGroup,
139
148
  removeGroup,
149
+ unwrapGroup,
140
150
  blockKey,
141
151
  isOpener,
142
152
  isClose,
@@ -187,7 +197,7 @@ var require_settings = __commonJS({
187
197
  // Edit view labels that are still the raw attribute name read as "Mobile columns count" (see README "Field labels").
188
198
  friendlyLabels: true,
189
199
  // Page preview pane (Strapi 5): which toolbar controls show, in order, and the width menu's entries.
190
- previewToolbar: ["modes", "history", "devices", "status", "actions"],
200
+ previewToolbar: ["modes", "history", "versions", "devices", "status", "actions"],
191
201
  previewDevices: ["fit", "mobile", "tablet", "desktop"]
192
202
  },
193
203
  // Per content type (only the ones with a Dynamic Zone): { enabled: false } turns the plugin off there;
@@ -202,7 +212,7 @@ var require_settings = __commonJS({
202
212
  var SIDEBAR_POSITIONS = ["left", "right", "bottom"];
203
213
  var OPENS = ["modal", "drawer"];
204
214
  var LABEL = /^[^<>]{1,40}$/;
205
- var TOOLBAR = ["modes", "history", "devices", "status", "actions"];
215
+ var TOOLBAR = ["modes", "history", "versions", "devices", "status", "actions"];
206
216
  var DEVICE_NAMES = ["fit", "mobile", "tablet", "desktop"];
207
217
  var previewToolbar = (value) => Array.isArray(value) && value.every((id) => TOOLBAR.includes(id)) && new Set(value).size === value.length;
208
218
  var DEVICE_LABEL = /^[^<>]{1,24}$/;
@@ -239,6 +249,13 @@ var require_settings = __commonJS({
239
249
  var TAG = /^[^<>\n]{1,24}$/;
240
250
  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) : [];
241
251
  var validTags = (value) => Array.isArray(value) && value.length <= 10 && value.every((tag) => typeof tag === "string" && TAG.test(tag.trim()));
252
+ var directComponentUids = (contentTypes, perType = {}) => [...new Set(Object.entries(contentTypes || {}).filter(([uid]) => perType?.[uid]?.enabled !== false).flatMap(([, schema]) => Object.values(schema?.attributes || {}).filter((attr) => attr?.type === "dynamiczone").flatMap((attr) => attr.components || [])))];
253
+ var COMPONENT_CATEGORY = /^[a-z0-9][a-z0-9_-]{0,49}$/;
254
+ var safeComponentCategories2 = (value) => value == null ? null : Array.isArray(value) && value.length > 0 && value.length <= 50 && value.every((category) => typeof category === "string" && COMPONENT_CATEGORY.test(category)) ? [...new Set(value)] : null;
255
+ var settingsComponentUids2 = (contentTypes, components, perType = {}, categories = null) => {
256
+ const allowed = safeComponentCategories2(categories);
257
+ return directComponentUids(contentTypes, perType).filter((uid) => !allowed || allowed.includes(components?.[uid]?.category || uid.split(".")[0]));
258
+ };
242
259
  var GUESSES = [
243
260
  ["hero", /hero|banner|cover/],
244
261
  ["text", /text|rich|quote|title/],
@@ -301,6 +318,7 @@ var require_settings = __commonJS({
301
318
  ["label", "description", "category", "keywords"].filter((key) => typeof entry[key] === "string").map((key) => [key, entry[key]])
302
319
  );
303
320
  entries[uid].image = safeUrl2(entry.image);
321
+ if (typeof entry.showRowThumbnail === "boolean") entries[uid].showRowThumbnail = entry.showRowThumbnail;
304
322
  if (TYPOLOGIES2.includes(entry.typology)) entries[uid].typology = entry.typology;
305
323
  const tags = cleanTags(entry.tags);
306
324
  if (tags.length || entries[uid].category) entries[uid].tags = tags.length ? tags : cleanTags([entries[uid].category]);
@@ -358,6 +376,9 @@ var require_settings = __commonJS({
358
376
  } else if (key === "mediaId") {
359
377
  if (!Number.isInteger(entry.mediaId) || entry.mediaId <= 0) fail2(`Invalid media for "${uid}"`);
360
378
  clean.mediaId = entry.mediaId;
379
+ } else if (key === "showRowThumbnail") {
380
+ if (typeof entry.showRowThumbnail !== "boolean") fail2(`Invalid row thumbnail setting for "${uid}"`);
381
+ clean.showRowThumbnail = entry.showRowThumbnail;
361
382
  } else if (key === "layout") {
362
383
  if (!validLayout(entry.layout, schemas && (schemas[uid]?.attributes || {}))) fail2(`Invalid layout for "${uid}": columnsField (and mobileColumnsField) must name a number, string or numeric enumeration attribute of the component; maxColumns 1 to 12`);
363
384
  clean.layout = structuredClone(entry.layout);
@@ -416,6 +437,7 @@ var require_settings = __commonJS({
416
437
  clean.tags = cleanTags(clean.tags);
417
438
  if (!clean.tags.length) delete clean.tags;
418
439
  }
440
+ if ("showRowThumbnail" in clean && typeof clean.showRowThumbnail !== "boolean") delete clean.showRowThumbnail;
419
441
  if ("layout" in clean && !validLayout(clean.layout, schemas && (schemas[uid]?.attributes || {}))) delete clean.layout;
420
442
  if (Object.keys(clean).length) out.components[uid] = clean;
421
443
  }
@@ -624,6 +646,102 @@ var require_settings = __commonJS({
624
646
  }
625
647
  return out;
626
648
  }
649
+ function requiredProblems(schema, values, schemas, path, media, problems) {
650
+ for (const [field, attr] of Object.entries(schema?.attributes || {})) {
651
+ const at = `${path}.${field}`;
652
+ const supplied = Object.hasOwn(values, field);
653
+ const value = supplied ? values[field] : attr.default;
654
+ if (attr.type === "media" || attr.type === "relation") {
655
+ if (attr.required && !supplied) media.push(at);
656
+ continue;
657
+ }
658
+ if (attr.type === "component") {
659
+ const nested = schemas[attr.component];
660
+ if (!nested) {
661
+ problems.push(`${at}: unknown component "${attr.component}"`);
662
+ continue;
663
+ }
664
+ if (attr.repeatable) {
665
+ const minimum = Math.max(attr.min ?? 0, attr.required ? 1 : 0);
666
+ const items = supplied ? value : Array.from({ length: minimum }, () => ({}));
667
+ if (!Array.isArray(items) || items.length < minimum) problems.push(`${at} requires at least ${minimum} item${minimum === 1 ? "" : "s"}`);
668
+ else items.forEach((item, index) => requiredProblems(nested, item, schemas, `${at}[${index}]`, media, problems));
669
+ } else if (supplied && value === null) {
670
+ if (attr.required) problems.push(`${at} is required`);
671
+ } else if (supplied || attr.required) requiredProblems(nested, supplied ? value : {}, schemas, at, media, problems);
672
+ continue;
673
+ }
674
+ if (attr.required && (value === void 0 || value === null || value === "")) problems.push(`${at} is required`);
675
+ }
676
+ }
677
+ function validateKits2(input, contentTypes = {}, schemas = {}, groups = null, notes = []) {
678
+ if (input === null || input === void 0) return {};
679
+ if (!plain(input) || JSON.stringify(input).length > 256 * 1024) {
680
+ notes.push("expected an object up to 256 KB");
681
+ return {};
682
+ }
683
+ const out = {};
684
+ for (const [uid, kits] of Object.entries(input)) {
685
+ const type = contentTypes[uid];
686
+ if (!type || !uid.startsWith("api::")) {
687
+ notes.push(`${uid}: unknown content type`);
688
+ continue;
689
+ }
690
+ if (!Array.isArray(kits) || kits.length > 12) {
691
+ notes.push(`${uid}: kits must be a list of at most 12`);
692
+ continue;
693
+ }
694
+ const ids = /* @__PURE__ */ new Set();
695
+ for (const kit of kits) {
696
+ const name = `${uid} kit "${kit?.id ?? "?"}"`;
697
+ try {
698
+ if (!plain(kit) || Object.keys(kit).some((key) => !["id", "label", "locales", "zones"].includes(key))) throw new Error("expected { id, label, locales?, zones }");
699
+ 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');
700
+ if (!fieldText(kit.label, 60)) throw new Error('label must be a string of 1 to 60 characters, or { "<locale>": string }');
701
+ 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");
702
+ if (!plain(kit.zones) || !Object.keys(kit.zones).length) throw new Error("zones must be a non-empty object");
703
+ const zones = {};
704
+ for (const [zoneName, rows] of Object.entries(kit.zones)) {
705
+ const zone = type.attributes?.[zoneName];
706
+ if (zone?.type !== "dynamiczone") throw new Error(`unknown Dynamic Zone "${zoneName}"`);
707
+ if (!Array.isArray(rows) || !rows.length || rows.length > 50) throw new Error(`${zoneName} must contain 1 to 50 rows`);
708
+ const clean = [];
709
+ for (let index = 0; index < rows.length; index++) {
710
+ const row = rows[index];
711
+ if (!plain(row) || !text(row.__component)) throw new Error(`${zoneName}[${index}] must have __component`);
712
+ const component = row.__component;
713
+ if (!zone.components?.includes(component) || !schemas[component]) throw new Error(`${zoneName}[${index}]: component "${component}" is not allowed`);
714
+ const skipped = [];
715
+ const values = variantValues(row, schemas[component], schemas, `${zoneName}[${index}]`, skipped);
716
+ if (skipped.length) notes.push(`${name}: media and relation values are not inserted (${skipped.slice(0, 5).join(", ")}${skipped.length > 5 ? ", ..." : ""})`);
717
+ const media = [], required = [];
718
+ requiredProblems(schemas[component], values, schemas, `${zoneName}[${index}]`, media, required);
719
+ if (required.length) throw new Error(required.slice(0, 5).join("; "));
720
+ if (media.length) notes.push(`${name}: required media or relations must be completed before publishing (${media.slice(0, 5).join(", ")}${media.length > 5 ? ", ..." : ""})`);
721
+ clean.push({ __component: component, values });
722
+ const close = groups?.[component];
723
+ if (close) {
724
+ if (!zone.components?.includes(close) || !schemas[close]) throw new Error(`${zoneName}[${index}]: close component "${close}" is not allowed`);
725
+ clean.push({ __component: close, values: {} });
726
+ }
727
+ }
728
+ if (clean.length > (zone.max ?? Infinity)) throw new Error(`${zoneName} exceeds its maximum of ${zone.max}`);
729
+ zones[zoneName] = clean;
730
+ }
731
+ for (const [zoneName, zone] of Object.entries(type.attributes || {})) {
732
+ if (zone?.type !== "dynamiczone") continue;
733
+ const minimum = Math.max(zone.min ?? 0, zone.required ? 1 : 0);
734
+ if ((zones[zoneName]?.length || 0) < minimum) throw new Error(`${zoneName} requires at least ${minimum} row${minimum === 1 ? "" : "s"}`);
735
+ }
736
+ ids.add(kit.id);
737
+ (out[uid] ||= []).push({ id: kit.id, label: structuredClone(kit.label), ...kit.locales && { locales: [...new Set(kit.locales)] }, zones });
738
+ } catch (error) {
739
+ notes.push(`${name} ignored: ${error.message}`);
740
+ }
741
+ }
742
+ }
743
+ return out;
744
+ }
627
745
  var PREFS = { starred: 200, recent: 20 };
628
746
  function validatePrefs2(input, componentUids2) {
629
747
  if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Preferences must be an object");
@@ -636,7 +754,7 @@ var require_settings = __commonJS({
636
754
  return out;
637
755
  }
638
756
  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 };
757
+ module2.exports = { PLUGIN: PLUGIN2, HIDDEN_MODES, TEMPLATES: TEMPLATES2, TYPOLOGIES: TYPOLOGIES2, guessTypology, facetsOf, directComponentUids, settingsComponentUids: settingsComponentUids2, safeComponentCategories: safeComponentCategories2, 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
758
  }
641
759
  });
642
760
 
@@ -826,8 +944,8 @@ var require_diff = __commonJS({
826
944
  }
827
945
  function summarize(before, after, schema) {
828
946
  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 };
947
+ const blocks = { total: zones.reduce((n, name) => n + (Array.isArray(after?.[name]) ? after[name].length : 0), 0), added: 0, removed: 0, changed: 0 };
948
+ if (!before) return { initial: true, fields: [], blocks: { ...blocks, added: blocks.total } };
831
949
  const names = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after || {})])];
832
950
  const fields = names.filter((name) => !zones.includes(name) && !same(before[name], after?.[name])).sort();
833
951
  for (const name of zones) for (const { op } of diffRows(before[name], after?.[name])) if (op !== "same") blocks[op]++;
@@ -1298,7 +1416,6 @@ var require_history = __commonJS({
1298
1416
  var CRON2 = "blocksceneHistoryPurge";
1299
1417
  var BATCH = 500;
1300
1418
  var DAY = 24 * 60 * 60 * 1e3;
1301
- var LIST_LIMIT = 100;
1302
1419
  var ACTORS_LIMIT = 500;
1303
1420
  var SEARCH_LIMIT = 200;
1304
1421
  var eventContentType2 = { schema: {
@@ -1441,14 +1558,23 @@ var require_history = __commonJS({
1441
1558
  return name && name !== "id" && schemaOf(uid)?.attributes?.[name] && !["relation", "media", "component", "dynamiczone", "password", "json", "blocks"].includes(schemaOf(uid).attributes[name].type) ? name : null;
1442
1559
  },
1443
1560
  // 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 where = { contentType: uid, relatedDocumentId: documentId, locale: locale || null };
1446
- const [list, kept] = await Promise.all([
1447
- events().findMany({ select: ["id", "action", "actor", "actorName", "at", "summary", "hash", "size"], where, orderBy: [{ at: "desc" }, { id: "desc" }], limit: LIST_LIMIT }),
1448
- events().findMany({ select: ["hash"], where: { ...where, size: { $gt: 0 } } })
1561
+ async list({ uid, documentId, locale, actor = "", page = 1, pageSize = 25 }) {
1562
+ const scope2 = { contentType: uid, relatedDocumentId: documentId, locale: locale || null };
1563
+ const where = { ...scope2, ...actor && { actor } };
1564
+ const [list, kept, total, people] = await Promise.all([
1565
+ events().findMany({ select: ["id", "action", "actor", "actorName", "at", "summary", "hash", "size"], where, orderBy: [{ at: "desc" }, { id: "desc" }], offset: (page - 1) * pageSize, limit: pageSize }),
1566
+ events().findMany({ select: ["hash"], where: { ...scope2, size: { $gt: 0 } } }),
1567
+ events().count({ where }),
1568
+ events().findMany({ select: ["actor", "actorName"], where: scope2, orderBy: [{ at: "desc" }, { id: "desc" }] })
1449
1569
  ]);
1450
1570
  const hashes = new Set(kept.map((row) => row.hash));
1451
- return list.map(({ hash, size, ...event }) => ({ ...event, stored: Boolean(hash && hashes.has(hash)) }));
1571
+ const actors2 = [...new Map(people.slice().reverse().map(({ actor: actor2, actorName }) => [actor2, { actor: actor2, actorName }])).values()];
1572
+ return {
1573
+ results: list.map(({ hash, size, ...event }) => ({ ...event, stored: Boolean(hash && hashes.has(hash)) })),
1574
+ pagination: { page, pageSize, total, pageCount: Math.ceil(total / pageSize) },
1575
+ actors: actors2,
1576
+ locale: locale || null
1577
+ };
1452
1578
  },
1453
1579
  // One event with its snapshot (from an identical event when this one was deduplicated) and what loading it needs:
1454
1580
  // the media files and relation targets that still exist (relation labels from the Content Manager's main field).
@@ -1660,10 +1786,13 @@ var require_history = __commonJS({
1660
1786
  async list(ctx) {
1661
1787
  const { uid, documentId } = ctx.params || {};
1662
1788
  const locale = ctx.query?.locale || "";
1663
- 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");
1789
+ const actor = ctx.query?.actor || "";
1790
+ const page = int(ctx.query?.page, 1, 1e6), pageSize = int(ctx.query?.pageSize, 25, 100);
1791
+ if (typeof actor !== "string" || actor && !ACTOR.test(actor) || [page, pageSize].some(Number.isNaN)) return ctx.badRequest("Invalid history filters");
1792
+ 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
1793
  if (!canRead(strapi, ctx, uid)) return ctx.forbidden();
1665
1794
  const history = strapi.plugin(PLUGIN2).service("history");
1666
- ctx.body = { results: await history.list({ uid, documentId, locale: (await history.scope(uid, locale || void 0))[0] }) };
1795
+ ctx.body = await history.list({ uid, documentId, locale: (await history.scope(uid, locale || void 0))[0], actor, page, pageSize });
1667
1796
  },
1668
1797
  async find(ctx) {
1669
1798
  const id = Number(ctx.params?.id);
@@ -1690,7 +1819,7 @@ var require_history = __commonJS({
1690
1819
  });
1691
1820
 
1692
1821
  // server/index.js
1693
- var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1822
+ var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, settingsComponentUids, safeComponentCategories, validateSettings, validateFields, validateVariants, validateKits, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1694
1823
  var { safeGroups, validateGroups, invalidComponents, layoutFields } = require_groups();
1695
1824
  var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
1696
1825
  var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
@@ -1838,10 +1967,15 @@ module.exports = {
1838
1967
  // Optional layout groups: OPEN component uid -> its CLOSE uid, e.g. { 'wrappers.join': 'wrappers.close' }.
1839
1968
  // Empty/absent: every Dynamic Zone component is an ordinary block. See README "Layout groups".
1840
1969
  groups: null,
1970
+ // Optional Strapi component categories shown as configurable cards in Settings > Block gallery.
1971
+ // This does not change the components allowed by each Dynamic Zone.
1972
+ settingsComponentCategories: null,
1841
1973
  // Project defaults from code: same shape as the stored settings, e.g. require('./blockscene.json'). See README "Settings page".
1842
1974
  settings: null,
1843
1975
  // Edit view field texts from code (never stored): { uid: { attribute: { label, description, placeholder } } }. See README "Field labels".
1844
1976
  fields: null,
1977
+ // Initial block sets per content type, shown only on a new empty entry. See README "Starter kits".
1978
+ kits: null,
1845
1979
  // Other languages for the schema metadata (pluginOptions.blockscene, info.description), one flat map per locale:
1846
1980
  // { 'pt-BR': require('./blockscene/pt-BR.json') }. See README "Describing blocks and fields in the schema".
1847
1981
  translations: null,
@@ -1856,6 +1990,7 @@ module.exports = {
1856
1990
  const name = hiddenName(plugin.config("hiddenAttribute"));
1857
1991
  withHidden = [];
1858
1992
  if (name === null) strapi.log.warn(`[${PLUGIN}] "hiddenAttribute" config ignored: expected a name like "bsHidden" or false. Hiding blocks is off.`);
1993
+ if (plugin.config("settingsComponentCategories") != null && !safeComponentCategories(plugin.config("settingsComponentCategories"))) strapi.log.warn(`[${PLUGIN}] "settingsComponentCategories" config ignored: expected 1 to 50 Strapi component category names.`);
1859
1994
  if (!name) return;
1860
1995
  const { added, skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
1861
1996
  withHidden = added;
@@ -1876,6 +2011,7 @@ module.exports = {
1876
2011
  strapi.plugin(PLUGIN).service("settings").projectDefaults();
1877
2012
  strapi.plugin(PLUGIN).service("settings").fields();
1878
2013
  strapi.plugin(PLUGIN).service("settings").variants();
2014
+ strapi.plugin(PLUGIN).service("settings").kits();
1879
2015
  registerZoneGuards(strapi);
1880
2016
  registerHiddenStrip(strapi, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")), strapi.plugin(PLUGIN));
1881
2017
  if (withHidden.length) hideFromBuilder(typeof strapi.get === "function" ? strapi.get("services") : strapi.container.get("services"), withHidden, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")));
@@ -1934,6 +2070,14 @@ module.exports = {
1934
2070
  for (const note of notes) strapi.log?.warn(`[${PLUGIN}] components variants: ${note}.`);
1935
2071
  return variants;
1936
2072
  };
2073
+ let kits;
2074
+ const starterKits = () => {
2075
+ if (kits !== void 0) return kits;
2076
+ const notes = [];
2077
+ kits = validateKits(strapi.plugin(PLUGIN).config("kits"), strapi.contentTypes || {}, strapi.components || {}, safeGroups(strapi.plugin(PLUGIN).config("groups"), componentUids(strapi)), notes);
2078
+ for (const note of notes) strapi.log?.warn(`[${PLUGIN}] starter kits: ${note}.`);
2079
+ return kits;
2080
+ };
1937
2081
  const get = async () => mergeSaved(layer(projectDefaults(), await readSettings(strapi)), componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1938
2082
  return {
1939
2083
  projectDefaults,
@@ -1941,6 +2085,7 @@ module.exports = {
1941
2085
  fields: fieldTexts,
1942
2086
  schema: schemaMeta,
1943
2087
  variants: insertVariants,
2088
+ kits: starterKits,
1944
2089
  async set(value) {
1945
2090
  const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1946
2091
  for (const [uid, entry] of Object.entries(next.components)) {
@@ -1981,7 +2126,8 @@ module.exports = {
1981
2126
  ...media[uid],
1982
2127
  template: entry.template,
1983
2128
  ...entry.typology && { typology: entry.typology },
1984
- ...entry.tags && { tags: entry.tags }
2129
+ ...entry.tags && { tags: entry.tags },
2130
+ ...typeof entry.showRowThumbnail === "boolean" && { showRowThumbnail: entry.showRowThumbnail }
1985
2131
  };
1986
2132
  }
1987
2133
  const hidden = hiddenName(plugin.config("hiddenAttribute")) || null;
@@ -1991,6 +2137,7 @@ module.exports = {
1991
2137
  ctx.body = {
1992
2138
  ...base,
1993
2139
  layouts,
2140
+ kits: plugin.service("settings").kits(),
1994
2141
  palette: settings.palette,
1995
2142
  contentTypes: settings.contentTypes,
1996
2143
  hiddenAttribute: hidden,
@@ -2005,7 +2152,8 @@ module.exports = {
2005
2152
  async find(ctx) {
2006
2153
  const settings = await strapi.plugin(PLUGIN).service("settings").get();
2007
2154
  const auto = catalog({ components: strapi.plugin(PLUGIN).config("components"), schemas: strapi.components, blocks: strapi.plugin(PLUGIN).service("settings").schema().blocks }).components;
2008
- const components = Object.entries(strapi.components || {}).map(([uid, schema]) => ({
2155
+ const visible = new Set(settingsComponentUids(strapi.contentTypes, strapi.components, settings.contentTypes, strapi.plugin(PLUGIN).config("settingsComponentCategories")));
2156
+ const components = Object.entries(strapi.components || {}).filter(([uid]) => visible.has(uid)).map(([uid, schema]) => ({
2009
2157
  uid,
2010
2158
  displayName: schema.info?.displayName || uid,
2011
2159
  category: schema.category || uid.split(".")[0],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aduptive/strapi-blockscene",
3
- "version": "2.0.0-alpha.11",
3
+ "version": "2.0.0-alpha.13",
4
4
  "description": "Visual component gallery for native Strapi dynamic zones.",
5
5
  "license": "MIT",
6
6
  "author": "Andrea Scarpello <aduptive@gmail.com>",