@aduptive/strapi-blockscene 2.0.0-alpha.12 → 2.0.0-alpha.14

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,
@@ -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
  }
@@ -732,7 +754,7 @@ var require_settings = __commonJS({
732
754
  return out;
733
755
  }
734
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) : []]));
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 };
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 };
736
758
  }
737
759
  });
738
760
 
@@ -1438,6 +1460,18 @@ var require_history = __commonJS({
1438
1460
  var hashOf = (text) => createHash("sha256").update(text).digest("hex");
1439
1461
  var covers2 = (history, uid) => Boolean(history?.enabled) && uid.startsWith("api::") && (history.contentTypes === "all" || Array.isArray(history.contentTypes) && history.contentTypes.includes(uid));
1440
1462
  var retention = ({ retentionDays, eventDays }, now = Date.now()) => ({ snapshotsBefore: new Date(now - retentionDays * DAY), eventsBefore: new Date(now - eventDays * DAY) });
1463
+ var hasChanges = (summary) => Boolean(summary?.initial || summary?.fields?.length || ["added", "removed", "changed"].some((key) => summary?.blocks?.[key] > 0));
1464
+ function collapseVersions(events) {
1465
+ const versions = [];
1466
+ for (const event of events) {
1467
+ const previous = versions[versions.length - 1];
1468
+ if (event.hash && previous?.hash === event.hash) {
1469
+ if (!previous.actions.includes(event.action)) previous.actions.push(event.action);
1470
+ if (!hasChanges(previous.summary) && hasChanges(event.summary)) previous.summary = event.summary;
1471
+ } else versions.push({ ...event, actions: [event.action] });
1472
+ }
1473
+ return versions;
1474
+ }
1441
1475
  function actorOf(strapi) {
1442
1476
  let ctx;
1443
1477
  try {
@@ -1539,12 +1573,14 @@ var require_history = __commonJS({
1539
1573
  async list({ uid, documentId, locale, actor = "", page = 1, pageSize = 25 }) {
1540
1574
  const scope2 = { contentType: uid, relatedDocumentId: documentId, locale: locale || null };
1541
1575
  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 }),
1576
+ const [timeline, kept, people] = await Promise.all([
1577
+ events().findMany({ select: ["id", "action", "actor", "actorName", "at", "summary", "hash", "size"], where, orderBy: [{ at: "desc" }, { id: "desc" }] }),
1544
1578
  events().findMany({ select: ["hash"], where: { ...scope2, size: { $gt: 0 } } }),
1545
- events().count({ where }),
1546
1579
  events().findMany({ select: ["actor", "actorName"], where: scope2, orderBy: [{ at: "desc" }, { id: "desc" }] })
1547
1580
  ]);
1581
+ const versions = collapseVersions(timeline);
1582
+ const total = versions.length;
1583
+ const list = versions.slice((page - 1) * pageSize, page * pageSize);
1548
1584
  const hashes = new Set(kept.map((row) => row.hash));
1549
1585
  const actors2 = [...new Map(people.slice().reverse().map(({ actor: actor2, actorName }) => [actor2, { actor: actor2, actorName }])).values()];
1550
1586
  return {
@@ -1792,12 +1828,12 @@ var require_history = __commonJS({
1792
1828
  };
1793
1829
  }
1794
1830
  });
1795
- module2.exports = { EVENT_UID, ACTIONS, CAPTURED, CRON: CRON2, BATCH, eventContentType: eventContentType2, covers: covers2, retention, actorOf, refsOf, can, readableTypes, historyService: historyService2, registerHistory: registerHistory2, registerPurge: registerPurge2, historyController: historyController2 };
1831
+ module2.exports = { EVENT_UID, ACTIONS, CAPTURED, CRON: CRON2, BATCH, eventContentType: eventContentType2, covers: covers2, retention, actorOf, refsOf, collapseVersions, can, readableTypes, historyService: historyService2, registerHistory: registerHistory2, registerPurge: registerPurge2, historyController: historyController2 };
1796
1832
  }
1797
1833
  });
1798
1834
 
1799
1835
  // server/index.js
1800
- var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, validateKits, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1836
+ var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, settingsComponentUids, safeComponentCategories, validateSettings, validateFields, validateVariants, validateKits, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1801
1837
  var { safeGroups, validateGroups, invalidComponents, layoutFields } = require_groups();
1802
1838
  var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
1803
1839
  var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
@@ -1945,6 +1981,9 @@ module.exports = {
1945
1981
  // Optional layout groups: OPEN component uid -> its CLOSE uid, e.g. { 'wrappers.join': 'wrappers.close' }.
1946
1982
  // Empty/absent: every Dynamic Zone component is an ordinary block. See README "Layout groups".
1947
1983
  groups: null,
1984
+ // Optional Strapi component categories shown as configurable cards in Settings > Block gallery.
1985
+ // This does not change the components allowed by each Dynamic Zone.
1986
+ settingsComponentCategories: null,
1948
1987
  // Project defaults from code: same shape as the stored settings, e.g. require('./blockscene.json'). See README "Settings page".
1949
1988
  settings: null,
1950
1989
  // Edit view field texts from code (never stored): { uid: { attribute: { label, description, placeholder } } }. See README "Field labels".
@@ -1965,6 +2004,7 @@ module.exports = {
1965
2004
  const name = hiddenName(plugin.config("hiddenAttribute"));
1966
2005
  withHidden = [];
1967
2006
  if (name === null) strapi.log.warn(`[${PLUGIN}] "hiddenAttribute" config ignored: expected a name like "bsHidden" or false. Hiding blocks is off.`);
2007
+ if (plugin.config("settingsComponentCategories") != null && !safeComponentCategories(plugin.config("settingsComponentCategories"))) strapi.log.warn(`[${PLUGIN}] "settingsComponentCategories" config ignored: expected 1 to 50 Strapi component category names.`);
1968
2008
  if (!name) return;
1969
2009
  const { added, skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
1970
2010
  withHidden = added;
@@ -2100,7 +2140,8 @@ module.exports = {
2100
2140
  ...media[uid],
2101
2141
  template: entry.template,
2102
2142
  ...entry.typology && { typology: entry.typology },
2103
- ...entry.tags && { tags: entry.tags }
2143
+ ...entry.tags && { tags: entry.tags },
2144
+ ...typeof entry.showRowThumbnail === "boolean" && { showRowThumbnail: entry.showRowThumbnail }
2104
2145
  };
2105
2146
  }
2106
2147
  const hidden = hiddenName(plugin.config("hiddenAttribute")) || null;
@@ -2125,7 +2166,8 @@ module.exports = {
2125
2166
  async find(ctx) {
2126
2167
  const settings = await strapi.plugin(PLUGIN).service("settings").get();
2127
2168
  const auto = catalog({ components: strapi.plugin(PLUGIN).config("components"), schemas: strapi.components, blocks: strapi.plugin(PLUGIN).service("settings").schema().blocks }).components;
2128
- const components = Object.entries(strapi.components || {}).map(([uid, schema]) => ({
2169
+ const visible = new Set(settingsComponentUids(strapi.contentTypes, strapi.components, settings.contentTypes, strapi.plugin(PLUGIN).config("settingsComponentCategories")));
2170
+ const components = Object.entries(strapi.components || {}).filter(([uid]) => visible.has(uid)).map(([uid, schema]) => ({
2129
2171
  uid,
2130
2172
  displayName: schema.info?.displayName || uid,
2131
2173
  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.12",
3
+ "version": "2.0.0-alpha.14",
4
4
  "description": "Visual component gallery for native Strapi dynamic zones.",
5
5
  "license": "MIT",
6
6
  "author": "Andrea Scarpello <aduptive@gmail.com>",