@aduptive/strapi-blockscene 2.0.0-alpha.7 → 2.0.0-alpha.9

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
@@ -106,7 +106,41 @@ var require_groups = __commonJS({
106
106
  const [, end] = groupRange(rows, start, g);
107
107
  return [...rows.slice(0, start), ...rows.slice(end + 1)];
108
108
  }
109
- module2.exports = { safeGroups: safeGroups2, validateGroups: validateGroups2, groupRange, groupRows, topLevelRanges, moveGroup, removeGroup, blockKey, isOpener, isClose };
109
+ var MAX_COLUMNS = 12;
110
+ var parseColumns = (value) => {
111
+ const n = typeof value === "number" ? value : typeof value === "string" && /^\s*\d{1,3}\s*$/.test(value) ? Number(value) : NaN;
112
+ return Number.isInteger(n) && n >= 1 ? n : null;
113
+ };
114
+ var columnsAttribute = (attr) => Boolean(attr) && typeof attr === "object" && !attr.private && (["integer", "biginteger", "float", "decimal", "string"].includes(attr.type) || attr.type === "enumeration" && Array.isArray(attr.enum) && attr.enum.length > 0 && attr.enum.every((value) => parseColumns(value) !== null));
115
+ var layoutFields2 = (schema) => Object.entries(schema?.attributes || {}).filter(([, attr]) => columnsAttribute(attr)).map(([name]) => name);
116
+ function validLayout(value, attributes) {
117
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
118
+ if (!Object.keys(value).every((key) => ["columnsField", "mobileColumnsField", "maxColumns"].includes(key))) return false;
119
+ const field = (name) => typeof name === "string" && /^[A-Za-z_][\w-]{0,63}$/.test(name) && (!attributes || columnsAttribute(attributes[name]));
120
+ return field(value.columnsField) && (value.mobileColumnsField === void 0 || field(value.mobileColumnsField)) && (value.maxColumns === void 0 || Number.isInteger(value.maxColumns) && value.maxColumns >= 1 && value.maxColumns <= MAX_COLUMNS);
121
+ }
122
+ function layoutColumns(row, layout, schema) {
123
+ const max = layout.maxColumns || MAX_COLUMNS;
124
+ const read = (name) => Math.min(max, parseColumns(row?.[name]) ?? parseColumns(schema?.attributes?.[name]?.default) ?? 1);
125
+ return { desktop: read(layout.columnsField), mobile: layout.mobileColumnsField ? read(layout.mobileColumnsField) : null };
126
+ }
127
+ module2.exports = {
128
+ safeGroups: safeGroups2,
129
+ validateGroups: validateGroups2,
130
+ groupRange,
131
+ groupRows,
132
+ topLevelRanges,
133
+ moveGroup,
134
+ removeGroup,
135
+ blockKey,
136
+ isOpener,
137
+ isClose,
138
+ MAX_COLUMNS,
139
+ parseColumns,
140
+ layoutFields: layoutFields2,
141
+ validLayout,
142
+ layoutColumns
143
+ };
110
144
  }
111
145
  });
112
146
 
@@ -237,7 +271,7 @@ var require_settings = __commonJS({
237
271
  return [...facets];
238
272
  }
239
273
  var safeUrl2 = (value) => typeof value === "string" && (/^\/(?!\/)/.test(value) || /^https?:\/\//.test(value)) ? value : void 0;
240
- var { safeGroups: safeGroups2 } = require_groups();
274
+ var { safeGroups: safeGroups2, validLayout } = require_groups();
241
275
  var safeVersion = (value) => typeof value === "string" && /^[\w.-]{1,32}$/.test(value) ? value : void 0;
242
276
  var fail2 = (message, details) => {
243
277
  let ValidationError;
@@ -268,6 +302,7 @@ var require_settings = __commonJS({
268
302
  }
269
303
  for (const [uid, schema] of Object.entries(config.schemas || {})) {
270
304
  const entry = entries[uid] ||= {};
305
+ for (const [key, value] of Object.entries(config.blocks?.[uid] || {})) if (entry[key] === void 0) entry[key] = value;
271
306
  entry.facets = facetsOf(uid, schema, config.schemas);
272
307
  entry.typology ||= guessTypology(uid, schema?.info?.displayName);
273
308
  entry.tags ||= [];
@@ -282,7 +317,7 @@ var require_settings = __commonJS({
282
317
  groups: safeGroups2(config.groups, config.componentUids)
283
318
  };
284
319
  }
285
- function validateSettings2(input, componentUids2, contentTypeUids2 = [], historyTypes = null) {
320
+ function validateSettings2(input, componentUids2, contentTypeUids2 = [], historyTypes = null, schemas = null) {
286
321
  const types = typeMap(contentTypeUids2);
287
322
  if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Settings must be an object");
288
323
  if (JSON.stringify(input).length > 64 * 1024) fail2("Settings payload too large");
@@ -318,6 +353,9 @@ var require_settings = __commonJS({
318
353
  } else if (key === "mediaId") {
319
354
  if (!Number.isInteger(entry.mediaId) || entry.mediaId <= 0) fail2(`Invalid media for "${uid}"`);
320
355
  clean.mediaId = entry.mediaId;
356
+ } else if (key === "layout") {
357
+ 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`);
358
+ clean.layout = structuredClone(entry.layout);
321
359
  } else fail2(`Unknown component setting "${key}"`);
322
360
  }
323
361
  if (Object.keys(clean).length) out.components[uid] = clean;
@@ -343,7 +381,7 @@ var require_settings = __commonJS({
343
381
  if (out.history.eventDays < out.history.retentionDays) fail2("history.eventDays must be at least history.retentionDays");
344
382
  return out;
345
383
  }
346
- function mergeSaved2(saved, componentUids2, contentTypeUids2 = [], historyTypes = null) {
384
+ function mergeSaved2(saved, componentUids2, contentTypeUids2 = [], historyTypes = null, schemas = null) {
347
385
  const types = typeMap(contentTypeUids2);
348
386
  const out = structuredClone(DEFAULTS2);
349
387
  if (!saved || typeof saved !== "object") return out;
@@ -373,6 +411,7 @@ var require_settings = __commonJS({
373
411
  clean.tags = cleanTags(clean.tags);
374
412
  if (!clean.tags.length) delete clean.tags;
375
413
  }
414
+ if ("layout" in clean && !validLayout(clean.layout, schemas && (schemas[uid]?.attributes || {}))) delete clean.layout;
376
415
  if (Object.keys(clean).length) out.components[uid] = clean;
377
416
  }
378
417
  for (const [uid, entry] of Object.entries(saved.contentTypes || {})) {
@@ -411,7 +450,7 @@ var require_settings = __commonJS({
411
450
  var FIELD_TEXT = { label: 80, description: 300, placeholder: 120, help: 500 };
412
451
  var LOCALE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
413
452
  var plain = (value) => value && typeof value === "object" && !Array.isArray(value);
414
- var fieldText = (value, max) => typeof value === "string" ? value.trim() !== "" && value.length <= max : plain(value) && Object.keys(value).length > 0 && Object.keys(value).length <= 30 && Object.entries(value).every(([locale, text]) => LOCALE.test(locale) && fieldText(text, max));
453
+ var fieldText = (value, max) => typeof value === "string" ? value.trim() !== "" && value.length <= max : plain(value) && Object.keys(value).length > 0 && Object.keys(value).length <= 30 && Object.entries(value).every(([locale, text2]) => LOCALE.test(locale) && fieldText(text2, max));
415
454
  function validateFields2(input, schemas = {}, stale = []) {
416
455
  if (input === null || input === void 0) return {};
417
456
  if (!plain(input)) fail2("fields must be an object");
@@ -435,6 +474,151 @@ var require_settings = __commonJS({
435
474
  }
436
475
  return out;
437
476
  }
477
+ var str = (max) => (value) => typeof value === "string" && value.trim() !== "" && value.length <= max;
478
+ var BLOCK_META = { label: str(80), typology: (value) => TYPOLOGIES2.includes(value), tags: validTags, keywords: str(500), image: (value) => safeUrl2(value) !== void 0 };
479
+ var localize = (holder, key, source, locale, text2) => {
480
+ holder[key] = { ...plain(holder[key]) ? holder[key] : { en: holder[key] ?? source }, [locale]: text2 };
481
+ };
482
+ function schemaMetadata2(schemas = {}, translations = null, notes = []) {
483
+ const blocks = {}, fields = {};
484
+ const own = (holder, where) => {
485
+ const value = holder?.pluginOptions?.blockscene;
486
+ if (value === void 0 || plain(value)) return value || {};
487
+ notes.push(`${where}: pluginOptions.blockscene must be an object`);
488
+ return {};
489
+ };
490
+ for (const [uid, schema] of Object.entries(schemas)) {
491
+ if (!uid.includes("::")) {
492
+ const block = {};
493
+ for (const [key, value] of Object.entries(own(schema, uid))) {
494
+ if (!Object.hasOwn(BLOCK_META, key)) notes.push(`${uid}: unknown key "${key}"`);
495
+ else if (!BLOCK_META[key](value)) notes.push(`${uid}: invalid ${key}`);
496
+ else block[key] = key === "tags" ? cleanTags(value) : value;
497
+ }
498
+ if (typeof schema?.info?.description === "string" && schema.info.description.trim()) block.description = schema.info.description;
499
+ if (Object.keys(block).length) blocks[uid] = block;
500
+ }
501
+ for (const [name, attr] of Object.entries(schema?.attributes || {})) {
502
+ const texts = {};
503
+ for (const [key, value] of Object.entries(own(attr, `${uid}.${name}`))) {
504
+ if (!Object.hasOwn(FIELD_TEXT, key)) notes.push(`${uid}.${name}: unknown key "${key}"`);
505
+ else if (!str(FIELD_TEXT[key])(value)) notes.push(`${uid}.${name}: ${key} must be a string of 1 to ${FIELD_TEXT[key]} characters`);
506
+ else texts[key] = value;
507
+ }
508
+ if (Object.keys(texts).length) (fields[uid] ||= {})[name] = texts;
509
+ }
510
+ }
511
+ if (translations != null && !plain(translations)) {
512
+ notes.push('translations must be { "<locale>": { "<key>": text } }');
513
+ translations = null;
514
+ }
515
+ const stale = [];
516
+ for (const [locale, entries] of Object.entries(translations || {})) {
517
+ if (!LOCALE.test(locale) || !plain(entries) || JSON.stringify(entries).length > 256 * 1024) {
518
+ notes.push(`translations "${locale}" ignored: expected a locale code with a flat { "<key>": text } map under 256 KB`);
519
+ continue;
520
+ }
521
+ for (const [key, text2] of Object.entries(entries)) {
522
+ const uid = Object.keys(schemas).find((uid2) => key === uid2 || key.startsWith(`${uid2}.`));
523
+ const [name, part = "label", extra] = uid ? key.slice(uid.length + 1).split(".") : [];
524
+ const block = uid && !uid.includes("::");
525
+ const target = block && key === uid ? [blocks[uid] ||= {}, "label", schemas[uid]?.info?.displayName || uid, 80] : block && key === `${uid}.description` ? [blocks[uid] ||= {}, "description", null, 300] : uid && Object.hasOwn(schemas[uid]?.attributes || {}, name) && Object.hasOwn(FIELD_TEXT, part) && extra === void 0 ? [null, part, null, FIELD_TEXT[part]] : null;
526
+ if (!target) {
527
+ stale.push(`${locale} "${key}"`);
528
+ continue;
529
+ }
530
+ const [holder, field, source, max] = target;
531
+ if (!str(max)(text2)) {
532
+ notes.push(`translations ${locale} "${key}": a string of 1 to ${max} characters`);
533
+ continue;
534
+ }
535
+ localize(holder || ((fields[uid] ||= {})[name] ||= {}), field, source, locale, text2);
536
+ }
537
+ }
538
+ if (stale.length) notes.push(`translation keys naming no block or field were skipped: ${stale.slice(0, 20).join(", ")}${stale.length > 20 ? ` and ${stale.length - 20} more` : ""}`);
539
+ return { blocks, fields };
540
+ }
541
+ var VARIANT = { id: /^[a-z0-9][a-z0-9_-]{0,39}$/, perComponent: 12, bytes: 16 * 1024, total: 256 * 1024, items: 100 };
542
+ var text = (value) => typeof value === "string";
543
+ var finite = (value) => typeof value === "number" && Number.isFinite(value);
544
+ var SCALARS = {
545
+ string: text,
546
+ text,
547
+ richtext: text,
548
+ email: text,
549
+ uid: text,
550
+ date: text,
551
+ datetime: text,
552
+ time: text,
553
+ timestamp: text,
554
+ integer: Number.isInteger,
555
+ biginteger: (value) => Number.isInteger(value) || text(value) && /^-?\d{1,19}$/.test(value),
556
+ float: finite,
557
+ decimal: finite,
558
+ boolean: (value) => typeof value === "boolean",
559
+ json: () => true,
560
+ customField: () => true,
561
+ blocks: Array.isArray,
562
+ enumeration: (value, attr) => (attr.enum || []).includes(value)
563
+ };
564
+ function variantValues(values, schema, schemas, path, skipped) {
565
+ if (!plain(values)) throw new Error(`${path} must be an object`);
566
+ const out = {};
567
+ for (const [name, value] of Object.entries(values)) {
568
+ if (name === "id" || name === "__component") continue;
569
+ const attr = schema.attributes?.[name], at = `${path}.${name}`;
570
+ if (!attr) throw new Error(`${at} is not an attribute of the component`);
571
+ if (attr.type === "media" || attr.type === "relation") {
572
+ skipped.push(at);
573
+ continue;
574
+ }
575
+ if (attr.type === "component") {
576
+ const nested = schemas[attr.component], max = Math.min(attr.max ?? Infinity, VARIANT.items);
577
+ if (!nested) throw new Error(`${at}: unknown component "${attr.component}"`);
578
+ if (!attr.repeatable) out[name] = value === null ? null : variantValues(value, nested, schemas, at, skipped);
579
+ else if (!Array.isArray(value) || value.length > max) throw new Error(`${at} must be a list of at most ${max} items`);
580
+ else out[name] = value.map((item, index) => variantValues(item, nested, schemas, `${at}[${index}]`, skipped));
581
+ } else if (value === null || SCALARS[attr.type]?.(value, attr)) out[name] = structuredClone(value);
582
+ else throw new Error(`${at}: ${SCALARS[attr.type] ? `not a valid ${attr.type}` : `${attr.type} values are not supported`}`);
583
+ }
584
+ return out;
585
+ }
586
+ function validateVariants2(config, schemas = {}, notes = []) {
587
+ const out = {};
588
+ let total = 0;
589
+ for (const [uid, entry] of Object.entries(plain(config) ? config : {})) {
590
+ if (!plain(entry) || entry.variants === void 0) continue;
591
+ if (!schemas[uid]) {
592
+ notes.push(`${uid}: unknown component`);
593
+ continue;
594
+ }
595
+ if (!Array.isArray(entry.variants) || entry.variants.length > VARIANT.perComponent) {
596
+ notes.push(`${uid}: variants must be a list of at most ${VARIANT.perComponent}`);
597
+ continue;
598
+ }
599
+ const ids = /* @__PURE__ */ new Set();
600
+ entry.variants.forEach((variant, index) => {
601
+ const name = `${uid} variant "${variant?.id ?? index}"`;
602
+ try {
603
+ if (!plain(variant) || Object.keys(variant).some((key) => !["id", "label", "values"].includes(key))) throw new Error("expected { id, label, values }");
604
+ if (!text(variant.id) || !VARIANT.id.test(variant.id) || ids.has(variant.id)) throw new Error('id must be 1 to 40 lowercase letters, digits, "-" or "_", unique per component');
605
+ if (!fieldText(variant.label, 60)) throw new Error('label must be a string of 1 to 60 characters, or { "<locale>": string }');
606
+ const skipped = [];
607
+ const values = variantValues(variant.values ?? {}, schemas[uid], schemas, "values", skipped);
608
+ const size = JSON.stringify(values).length;
609
+ if (size > VARIANT.bytes) throw new Error(`values over ${VARIANT.bytes / 1024} KB`);
610
+ if (total + size > VARIANT.total) throw new Error(`all variants together over ${VARIANT.total / 1024} KB`);
611
+ if (skipped.length) notes.push(`${name}: media and relation values are not inserted (${skipped.slice(0, 5).join(", ")}${skipped.length > 5 ? ", ..." : ""})`);
612
+ ids.add(variant.id);
613
+ total += size;
614
+ (out[uid] ||= []).push({ id: variant.id, label: structuredClone(variant.label), values });
615
+ } catch (error) {
616
+ notes.push(`${name} ignored: ${error.message}`);
617
+ }
618
+ });
619
+ }
620
+ return out;
621
+ }
438
622
  var PREFS = { starred: 200, recent: 20 };
439
623
  function validatePrefs2(input, componentUids2) {
440
624
  if (!input || typeof input !== "object" || Array.isArray(input)) fail2("Preferences must be an object");
@@ -447,7 +631,7 @@ var require_settings = __commonJS({
447
631
  return out;
448
632
  }
449
633
  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) : []]));
450
- 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, mergeSaved: mergeSaved2, layer: layer2, overrides: overrides2, safeUrl: safeUrl2, fail: fail2 };
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 };
451
635
  }
452
636
  });
453
637
 
@@ -475,6 +659,22 @@ var require_hidden = __commonJS({
475
659
  }
476
660
  return { added, skipped };
477
661
  }
662
+ function hideFromBuilder2(services, uids, name) {
663
+ const has = new Set(uids);
664
+ const drop = (attributes) => Array.isArray(attributes) ? attributes.filter((attr) => attr?.name !== name) : attributes && typeof attributes === "object" ? Object.fromEntries(Object.entries(attributes).filter(([key]) => key !== name)) : attributes;
665
+ const extend = (uid, wrap) => {
666
+ if (services?.get?.(uid)) services.extend(uid, wrap);
667
+ };
668
+ extend("plugin::content-type-builder.components", (service) => ({ ...service, formatComponent(component, ...rest) {
669
+ const out = service.formatComponent(component, ...rest);
670
+ return has.has(component?.uid) && out?.schema ? { ...out, schema: { ...out.schema, attributes: drop(out.schema.attributes) } } : out;
671
+ } }));
672
+ extend("plugin::content-type-builder.schema", (service) => ({ ...service, async getSchema(...args) {
673
+ const out = await service.getSchema(...args);
674
+ for (const uid of has) if (out?.components?.[uid]) out.components[uid] = { ...out.components[uid], attributes: drop(out.components[uid].attributes) };
675
+ return out;
676
+ } }));
677
+ }
478
678
  function stripHidden(data, schema, schemaOf, name, depth = 0) {
479
679
  if (!data || typeof data !== "object" || !schema || depth > 20) return data;
480
680
  if (Array.isArray(data)) {
@@ -533,7 +733,7 @@ var require_hidden = __commonJS({
533
733
  }
534
734
  }));
535
735
  }
536
- module2.exports = { DEFAULT_NAME, hiddenName: hiddenName2, zoneComponents, injectHidden: injectHidden2, stripHidden, registerHiddenStrip: registerHiddenStrip2 };
736
+ module2.exports = { DEFAULT_NAME, hiddenName: hiddenName2, zoneComponents, injectHidden: injectHidden2, hideFromBuilder: hideFromBuilder2, stripHidden, registerHiddenStrip: registerHiddenStrip2 };
537
737
  }
538
738
  });
539
739
 
@@ -1485,12 +1685,13 @@ var require_history = __commonJS({
1485
1685
  });
1486
1686
 
1487
1687
  // server/index.js
1488
- var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
1489
- var { safeGroups, validateGroups } = require_groups();
1490
- var { hiddenName, injectHidden, registerHiddenStrip } = require_hidden();
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();
1690
+ var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
1491
1691
  var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
1492
1692
  var { trashContentType, trashService, trashController } = require_trash();
1493
1693
  var STRAPI5 = true;
1694
+ var withHidden = [];
1494
1695
  var store = (strapi) => strapi.store({ type: "plugin", name: PLUGIN });
1495
1696
  var LEGACY_PLUGIN = "block-picker";
1496
1697
  async function readSettings(strapi) {
@@ -1611,6 +1812,9 @@ module.exports = {
1611
1812
  settings: null,
1612
1813
  // Edit view field texts from code (never stored): { uid: { attribute: { label, description, placeholder } } }. See README "Field labels".
1613
1814
  fields: null,
1815
+ // Other languages for the schema metadata (pluginOptions.blockscene, info.description), one flat map per locale:
1816
+ // { 'pt-BR': require('./blockscene/pt-BR.json') }. See README "Describing blocks and fields in the schema".
1817
+ translations: null,
1614
1818
  // "Hide on the site": boolean attribute added to every Dynamic Zone component (a DB column); false adds nothing.
1615
1819
  hiddenAttribute: "bsHidden"
1616
1820
  },
@@ -1620,9 +1824,11 @@ module.exports = {
1620
1824
  register({ strapi }) {
1621
1825
  const plugin = strapi.plugin(PLUGIN);
1622
1826
  const name = hiddenName(plugin.config("hiddenAttribute"));
1827
+ withHidden = [];
1623
1828
  if (name === null) strapi.log.warn(`[${PLUGIN}] "hiddenAttribute" config ignored: expected a name like "bsHidden" or false. Hiding blocks is off.`);
1624
1829
  if (!name) return;
1625
- const { skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
1830
+ const { added, skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
1831
+ withHidden = added;
1626
1832
  if (skipped.length) strapi.log.warn(`[${PLUGIN}] "${name}" already exists with another type on ${skipped.join(", ")}; those blocks cannot be hidden.`);
1627
1833
  },
1628
1834
  async bootstrap({ strapi }) {
@@ -1639,8 +1845,10 @@ module.exports = {
1639
1845
  ]);
1640
1846
  strapi.plugin(PLUGIN).service("settings").projectDefaults();
1641
1847
  strapi.plugin(PLUGIN).service("settings").fields();
1848
+ strapi.plugin(PLUGIN).service("settings").variants();
1642
1849
  registerPublishGuard(strapi);
1643
1850
  registerHiddenStrip(strapi, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")), strapi.plugin(PLUGIN));
1851
+ if (withHidden.length) hideFromBuilder(typeof strapi.get === "function" ? strapi.get("services") : strapi.container.get("services"), withHidden, hiddenName(strapi.plugin(PLUGIN).config("hiddenAttribute")));
1644
1852
  if (STRAPI5) {
1645
1853
  registerHistory(strapi);
1646
1854
  registerPurge(strapi);
@@ -1658,33 +1866,53 @@ module.exports = {
1658
1866
  const code = strapi.plugin(PLUGIN).config("settings");
1659
1867
  project = null;
1660
1868
  if (code) try {
1661
- project = validateSettings(code, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi));
1869
+ project = validateSettings(code, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1662
1870
  } catch (error) {
1663
1871
  strapi.log?.warn(`[${PLUGIN}] "settings" config ignored: ${error.message}. Using the built-in defaults under the saved settings.`);
1664
1872
  }
1665
1873
  return project;
1666
1874
  };
1875
+ let meta;
1876
+ const schemaMeta = () => {
1877
+ if (meta !== void 0) return meta;
1878
+ const notes = [];
1879
+ meta = schemaMetadata({ ...strapi.contentTypes, ...strapi.components }, strapi.plugin(PLUGIN).config("translations"), notes);
1880
+ if (notes.length) strapi.log?.warn(`[${PLUGIN}] schema metadata left out: ${notes.slice(0, 20).join("; ")}${notes.length > 20 ? `; and ${notes.length - 20} more` : ""}.`);
1881
+ return meta;
1882
+ };
1667
1883
  let fields;
1668
1884
  const fieldTexts = () => {
1669
1885
  if (fields !== void 0) return fields;
1670
1886
  const schemas = Object.fromEntries(Object.entries({ ...strapi.contentTypes, ...strapi.components }).map(([uid, schema]) => [uid, Object.keys(schema?.attributes || {})]));
1671
1887
  const stale = [];
1888
+ let code = {};
1672
1889
  try {
1673
- fields = validateFields(strapi.plugin(PLUGIN).config("fields"), schemas, stale);
1890
+ code = validateFields(strapi.plugin(PLUGIN).config("fields"), schemas, stale);
1674
1891
  } catch (error) {
1675
- fields = {};
1676
- strapi.log?.warn(`[${PLUGIN}] "fields" config ignored: ${error.message}. Field labels fall back to the Content Manager's own.`);
1892
+ strapi.log?.warn(`[${PLUGIN}] "fields" config ignored: ${error.message}. Field labels fall back to the schema metadata and the Content Manager's own.`);
1677
1893
  }
1678
1894
  if (stale.length) strapi.log?.warn(`[${PLUGIN}] "fields" entries for fields the schema no longer has were skipped: ${stale.slice(0, 20).join(", ")}${stale.length > 20 ? ` and ${stale.length - 20} more` : ""}.`);
1895
+ fields = structuredClone(schemaMeta().fields);
1896
+ for (const [uid, entries] of Object.entries(code)) for (const [name, entry] of Object.entries(entries)) (fields[uid] ||= {})[name] = { ...fields[uid][name], ...entry };
1679
1897
  return fields;
1680
1898
  };
1681
- const get = async () => mergeSaved(layer(projectDefaults(), await readSettings(strapi)), componentUids(strapi), contentTypeUids(strapi), apiUids(strapi));
1899
+ let variants;
1900
+ const insertVariants = () => {
1901
+ if (variants !== void 0) return variants;
1902
+ const notes = [];
1903
+ variants = validateVariants(strapi.plugin(PLUGIN).config("components"), strapi.components || {}, notes);
1904
+ for (const note of notes) strapi.log?.warn(`[${PLUGIN}] components variants: ${note}.`);
1905
+ return variants;
1906
+ };
1907
+ const get = async () => mergeSaved(layer(projectDefaults(), await readSettings(strapi)), componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1682
1908
  return {
1683
1909
  projectDefaults,
1684
1910
  get,
1685
1911
  fields: fieldTexts,
1912
+ schema: schemaMeta,
1913
+ variants: insertVariants,
1686
1914
  async set(value) {
1687
- const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi));
1915
+ const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
1688
1916
  for (const [uid, entry] of Object.entries(next.components)) {
1689
1917
  if (entry.mediaId && !await findMedia(strapi, entry.mediaId).catch(() => null)) fail(`Media for "${uid}" does not exist`);
1690
1918
  }
@@ -1711,10 +1939,12 @@ module.exports = {
1711
1939
  blockPreview: plugin.config("blockPreview"),
1712
1940
  groups: plugin.config("groups"),
1713
1941
  componentUids: componentUids(strapi),
1714
- schemas: strapi.components
1942
+ schemas: strapi.components,
1943
+ blocks: plugin.service("settings").schema().blocks
1715
1944
  });
1716
1945
  const settings = await plugin.service("settings").get();
1717
1946
  const media = await resolveMedia(strapi, settings);
1947
+ for (const [uid, list] of Object.entries(plugin.service("settings").variants())) base.components[uid] = { ...base.components[uid], variants: list };
1718
1948
  for (const [uid, entry] of Object.entries(settings.components)) {
1719
1949
  base.components[uid] = {
1720
1950
  ...base.components[uid],
@@ -1727,8 +1957,10 @@ module.exports = {
1727
1957
  const hidden = hiddenName(plugin.config("hiddenAttribute")) || null;
1728
1958
  const types = Object.fromEntries(Object.entries(strapi.contentTypes || {}).filter(([uid]) => !uid.startsWith("admin::")).map(([uid, schema]) => [uid, [schema.info?.displayName || "", ...Object.keys(schema.attributes || {})]]));
1729
1959
  const history = STRAPI5 && strapi.documents?.use && !base.disabled && settings.history.enabled ? { contentTypes: apiUids(strapi).filter((uid) => covers(settings.history, uid)) } : null;
1960
+ const layouts = Object.fromEntries(Object.entries(settings.components).filter(([uid, entry]) => entry.layout && base.groups?.[uid]).map(([uid, entry]) => [uid, entry.layout]));
1730
1961
  ctx.body = {
1731
1962
  ...base,
1963
+ layouts,
1732
1964
  palette: settings.palette,
1733
1965
  contentTypes: settings.contentTypes,
1734
1966
  hiddenAttribute: hidden,
@@ -1742,12 +1974,13 @@ module.exports = {
1742
1974
  settings: ({ strapi }) => ({
1743
1975
  async find(ctx) {
1744
1976
  const settings = await strapi.plugin(PLUGIN).service("settings").get();
1745
- const auto = catalog({ components: strapi.plugin(PLUGIN).config("components"), schemas: strapi.components }).components;
1977
+ const auto = catalog({ components: strapi.plugin(PLUGIN).config("components"), schemas: strapi.components, blocks: strapi.plugin(PLUGIN).service("settings").schema().blocks }).components;
1746
1978
  const components = Object.entries(strapi.components || {}).map(([uid, schema]) => ({
1747
1979
  uid,
1748
1980
  displayName: schema.info?.displayName || uid,
1749
1981
  category: schema.category || uid.split(".")[0],
1750
- typology: auto[uid]?.typology
1982
+ typology: auto[uid]?.typology,
1983
+ layoutFields: layoutFields(schema)
1751
1984
  }));
1752
1985
  const contentTypes = Object.keys(contentTypeUids(strapi)).map((uid) => ({
1753
1986
  uid,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aduptive/strapi-blockscene",
3
- "version": "2.0.0-alpha.7",
3
+ "version": "2.0.0-alpha.9",
4
4
  "description": "Visual component gallery for native Strapi dynamic zones.",
5
5
  "license": "MIT",
6
6
  "author": "Andrea Scarpello <aduptive@gmail.com>",