@aduptive/strapi-blockscene 1.0.0-alpha.8 → 1.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/README.md +97 -6
- package/dist/admin.cjs +21 -7
- package/dist/admin.mjs +21 -7
- package/dist/server.cjs +110 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,9 +147,10 @@ layout, guessed from the component name and display name (hero, banner,
|
|
|
147
147
|
cover; text, rich, quote, title; image, media, video, gallery, carousel; list,
|
|
148
148
|
query, archive, related, posts, projects; card; cta, button, link; form,
|
|
149
149
|
contact; wrapper, column, grid, divider, spacer, section; else text). Override
|
|
150
|
-
it, and add up to 10 tags (1 to 24 characters), in Settings, Blockscene,
|
|
151
|
-
the plugin config
|
|
152
|
-
|
|
150
|
+
it, and add up to 10 tags (1 to 24 characters), in Settings, Blockscene, in
|
|
151
|
+
the plugin config (`components[uid].typology` and `components[uid].tags`) or in
|
|
152
|
+
the component schema ([`pluginOptions.blockscene`](#describing-blocks-and-fields-in-the-schema)).
|
|
153
|
+
A Settings value wins over the config, the config over the schema. The former `category` config key still
|
|
153
154
|
works: it becomes the block's tag when no tags are set.
|
|
154
155
|
|
|
155
156
|
The native "Add a component to <zone>" button of an editable zone opens this
|
|
@@ -269,7 +270,16 @@ as an input: the plugin removes it from the components' edit layouts through
|
|
|
269
270
|
the Content Manager's layout hook (it may be listed in "Configure the view" of
|
|
270
271
|
a component; leave it out of the layout). It is not marked `visible: false`,
|
|
271
272
|
because before Strapi 5.45 the edit view crashes on a value whose attribute the
|
|
272
|
-
admin does not know.
|
|
273
|
+
admin does not know. It is kept out of the Content-Type Builder instead (the
|
|
274
|
+
plugin wraps the builder's component reads), so saving a component there in
|
|
275
|
+
`strapi develop` never writes it to the component's JSON file. With earlier
|
|
276
|
+
versions (up to 2.0.0-alpha.8, and 1.0.0-alpha.8 on Strapi 4) the builder did
|
|
277
|
+
write it (`"bsHidden": { "type": "boolean", "configurable": false, "default": false }`);
|
|
278
|
+
a file that has it keeps working, and the builder keeps it there on later saves.
|
|
279
|
+
To clean it up, delete that entry from the component JSON by hand (the plugin
|
|
280
|
+
still adds the attribute at startup, so the column and its data stay). To check,
|
|
281
|
+
edit a Dynamic Zone component in the builder (for example its display name),
|
|
282
|
+
save, and diff its JSON file: only your change is there. Rename it with the plugin config
|
|
273
283
|
`hiddenAttribute: 'myName'`, or set `hiddenAttribute: false` to add nothing (no
|
|
274
284
|
eye, nothing stripped). A component that already has an attribute of that name
|
|
275
285
|
with another type is skipped with a warning. Removing the plugin (or setting
|
|
@@ -777,8 +787,10 @@ same hook the i18n plugin uses), so nothing in the DOM is patched and the
|
|
|
777
787
|
saved view configuration is never changed. Per field, the first that applies:
|
|
778
788
|
|
|
779
789
|
1. **Project texts** from the plugin config `fields` (below);
|
|
780
|
-
2. **
|
|
781
|
-
|
|
790
|
+
2. **schema metadata**: `pluginOptions.blockscene` on the attribute, with its
|
|
791
|
+
translations (see [Describing blocks and fields in the schema](#describing-blocks-and-fields-in-the-schema));
|
|
792
|
+
3. **a label set in "Configure the view"** (anything other than the attribute name);
|
|
793
|
+
4. **the humanized name** when "Friendly field labels" is on: `mobileColumnsCount`
|
|
782
794
|
reads "Mobile columns count", `page_seo` "Page seo", acronym runs stay
|
|
783
795
|
(`pageSEO` "Page SEO", `ctaURL` "Cta URL").
|
|
784
796
|
|
|
@@ -838,6 +850,85 @@ content type turned off all leave the native labels; any error leaves the
|
|
|
838
850
|
layout untouched. Changing the interface language applies from the next edit
|
|
839
851
|
view.
|
|
840
852
|
|
|
853
|
+
## Describing blocks and fields in the schema
|
|
854
|
+
|
|
855
|
+
The same texts can live in the component and content type JSON files, next to
|
|
856
|
+
what they describe, instead of the long `components` and `fields` config maps:
|
|
857
|
+
|
|
858
|
+
```jsonc
|
|
859
|
+
// src/components/sections/rich-text.json
|
|
860
|
+
{
|
|
861
|
+
"collectionName": "components_sections_rich_texts",
|
|
862
|
+
"info": { "displayName": "Rich text", "description": "Formatted text with an optional title and anchor." },
|
|
863
|
+
"pluginOptions": { "blockscene": { "tags": ["Editorial"] } },
|
|
864
|
+
"attributes": {
|
|
865
|
+
"anchor": { "type": "string", "pluginOptions": { "blockscene": { "help": "Id for links to this block (#id), without the \"#\"." } } },
|
|
866
|
+
"text": { "type": "richtext" }
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
// src/components/shared/tag.json, attribute "label"
|
|
870
|
+
"label": { "type": "string", "pluginOptions": { "blockscene": { "label": "Tag text", "help": "One or two words." } } }
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
- **Block**: the native `info.description` is the gallery description;
|
|
874
|
+
`pluginOptions.blockscene` takes `label`, `typology`, `tags`, `keywords` and
|
|
875
|
+
`image`, with the same rules as the `components` config entries.
|
|
876
|
+
- **Field** (any content type or component attribute): `pluginOptions.blockscene`
|
|
877
|
+
takes `label` (up to 80 characters), `description` (300), `placeholder` (120)
|
|
878
|
+
and `help` (500), each a plain string in the source language, English.
|
|
879
|
+
|
|
880
|
+
Other languages go in one flat file per locale, given to the plugin config:
|
|
881
|
+
|
|
882
|
+
```js
|
|
883
|
+
// config/plugins.js
|
|
884
|
+
blockscene: { config: { translations: { 'pt-BR': require('./blockscene/pt-BR.json') } } },
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
```json
|
|
888
|
+
{
|
|
889
|
+
"sections.rich-text": "Texto formatado",
|
|
890
|
+
"sections.rich-text.description": "Texto formatado com título e âncora opcionais.",
|
|
891
|
+
"sections.rich-text.anchor": "Âncora",
|
|
892
|
+
"sections.rich-text.anchor.help": "Id para links a este bloco (#id), sem o \"#\".",
|
|
893
|
+
"shared.tag.label": "Texto da tag"
|
|
894
|
+
}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Keys are `<uid>` (block label, else the `displayName`), `<uid>.description`
|
|
898
|
+
(block description), `<uid>.<attribute>` (field label) and
|
|
899
|
+
`<uid>.<attribute>.<description|placeholder|help>`. A component attribute named
|
|
900
|
+
`description` takes its label as `<uid>.description.label`. The admin picks the
|
|
901
|
+
language as for the field labels above; a missing key shows the schema's
|
|
902
|
+
English text, and a translated field label with no English one in the schema
|
|
903
|
+
shows Strapi's own label in English.
|
|
904
|
+
|
|
905
|
+
Precedence, per text: the plugin config (`components`, `fields`, and Settings
|
|
906
|
+
for typology and tags) > schema metadata > native Strapi ("Configure the view"
|
|
907
|
+
labels, `displayName`) > automatic (humanized names, guessed typology). The
|
|
908
|
+
existing config keeps working unchanged. Everything is read once at boot:
|
|
909
|
+
invalid values (a typology outside the list, a label over 80 characters, an
|
|
910
|
+
unknown key) and translation keys naming no known block or field are left out,
|
|
911
|
+
with one warning listing them by uid.
|
|
912
|
+
|
|
913
|
+
**Content-Type Builder.** Tested on Strapi 5.52.1 and 4.26.1 in development
|
|
914
|
+
mode: editing and saving a component in the Content-Type Builder keeps
|
|
915
|
+
`pluginOptions.blockscene` (block and attributes) and `info.description`. It
|
|
916
|
+
has no field for them, so add and change them in the JSON files.
|
|
917
|
+
|
|
918
|
+
To move an existing config, from a clone of this repository (idempotent; each
|
|
919
|
+
schema file keeps its indentation and key order; labels equal to what the
|
|
920
|
+
plugin would show anyway, the humanized name or the `displayName`, are dropped):
|
|
921
|
+
|
|
922
|
+
```bash
|
|
923
|
+
node scripts/schema-metadata.mjs ../my-project/backend --fields fields.json --components components.json [--out dir] [--source en] [--dry-run]
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
`fields.json` and `components.json` hold the two config maps as JSON. The
|
|
927
|
+
translation files go to `config/blockscene/<locale>.json`; the script lists
|
|
928
|
+
what it could not place and the `components` keys that stay in the config
|
|
929
|
+
(`variants`, `template`). Remove the migrated entries from the config
|
|
930
|
+
afterwards: the config wins.
|
|
931
|
+
|
|
841
932
|
## Version history (Strapi 5)
|
|
842
933
|
|
|
843
934
|
Off by default. Turn it on in Settings, Blockscene, "Version history" (or from
|
package/dist/admin.cjs
CHANGED
|
@@ -387,6 +387,11 @@ function localized(value, locale = "en") {
|
|
|
387
387
|
const key = keys.find((k) => k.toLowerCase() === lower) ?? keys.find((k) => k.toLowerCase() === lang(lower)) ?? keys.find((k) => lang(k) === lang(lower)) ?? keys.find((k) => lang(k) === "en") ?? keys[0];
|
|
388
388
|
return key === void 0 ? void 0 : value[key];
|
|
389
389
|
}
|
|
390
|
+
function localizeBlocks(catalog, locale) {
|
|
391
|
+
if (!catalog?.components) return catalog;
|
|
392
|
+
const text = (value) => localized(value, locale) ?? void 0;
|
|
393
|
+
return { ...catalog, components: Object.fromEntries(Object.entries(catalog.components).map(([uid, entry]) => [uid, entry && (typeof entry.label === "object" || typeof entry.description === "object") ? { ...entry, label: text(entry.label), description: text(entry.description) } : entry])) };
|
|
394
|
+
}
|
|
390
395
|
function fieldText(fields, owner, name, label, locale, on) {
|
|
391
396
|
const entry = owner && fields?.[owner]?.[name] || {};
|
|
392
397
|
const out = {};
|
|
@@ -394,7 +399,7 @@ function fieldText(fields, owner, name, label, locale, on) {
|
|
|
394
399
|
if (text !== void 0) out.label = text;
|
|
395
400
|
for (const key of ["description", "placeholder", "help"]) {
|
|
396
401
|
const value = localized(entry[key], locale);
|
|
397
|
-
if (value
|
|
402
|
+
if (value != null) out[key] = value;
|
|
398
403
|
}
|
|
399
404
|
return Object.keys(out).length ? out : null;
|
|
400
405
|
}
|
|
@@ -6222,7 +6227,7 @@ function ZoneGallery({
|
|
|
6222
6227
|
setConfig(null);
|
|
6223
6228
|
setDetail(null);
|
|
6224
6229
|
get("/blockscene/catalog").then(({ data }) => {
|
|
6225
|
-
if (active) setConfig(data);
|
|
6230
|
+
if (active) setConfig(localizeBlocks(data, t.locale));
|
|
6226
6231
|
}).catch(() => {
|
|
6227
6232
|
if (active) setError(t.failed);
|
|
6228
6233
|
});
|
|
@@ -6905,6 +6910,14 @@ var EdgeButton = import_styled_components6.default.button`
|
|
|
6905
6910
|
`;
|
|
6906
6911
|
var HOVER_ATTR = "data-blockscene-hover";
|
|
6907
6912
|
var HOVER_GRACE = 1200;
|
|
6913
|
+
var accordionCard = (row, ds) => {
|
|
6914
|
+
const toggle = row.querySelector("button[aria-expanded]");
|
|
6915
|
+
if (!toggle) return row;
|
|
6916
|
+
if (ds === 2) return toggle.closest("div[data-state]:has(> h3)") || row;
|
|
6917
|
+
for (let el = toggle.parentElement; el && el !== row; el = el.parentElement)
|
|
6918
|
+
if (getComputedStyle(el).borderTopStyle !== "none") return el;
|
|
6919
|
+
return row;
|
|
6920
|
+
};
|
|
6908
6921
|
var Frame2 = import_styled_components6.default.iframe`
|
|
6909
6922
|
position: absolute;
|
|
6910
6923
|
top: 0;
|
|
@@ -7570,7 +7583,8 @@ function PagePreview({
|
|
|
7570
7583
|
const item = hovered && active ? findZoneList(live.current.zoneLabel)?.querySelectorAll(":scope > li")[indexOf(hovered)] : void 0;
|
|
7571
7584
|
hoverRow.current = item || null;
|
|
7572
7585
|
if (!item) return setEdge(null);
|
|
7573
|
-
item
|
|
7586
|
+
const card = accordionCard(item, host.ds);
|
|
7587
|
+
card.setAttribute(HOVER_ATTR, "");
|
|
7574
7588
|
const measure = () => {
|
|
7575
7589
|
const r = item.getBoundingClientRect();
|
|
7576
7590
|
setEdge(r.bottom < 0 ? "up" : r.top > window.innerHeight ? "down" : null);
|
|
@@ -7579,7 +7593,7 @@ function PagePreview({
|
|
|
7579
7593
|
window.addEventListener("scroll", measure, true);
|
|
7580
7594
|
window.addEventListener("resize", measure);
|
|
7581
7595
|
return () => {
|
|
7582
|
-
|
|
7596
|
+
card.removeAttribute(HOVER_ATTR);
|
|
7583
7597
|
window.removeEventListener("scroll", measure, true);
|
|
7584
7598
|
window.removeEventListener("resize", measure);
|
|
7585
7599
|
};
|
|
@@ -7648,7 +7662,7 @@ function PagePreview({
|
|
|
7648
7662
|
},
|
|
7649
7663
|
children: [
|
|
7650
7664
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: `${SPLIT_STYLE}
|
|
7651
|
-
[${HOVER_ATTR}] {
|
|
7665
|
+
[${HOVER_ATTR}] { ${host.ds === 2 ? `box-shadow: 0 0 0 1px ${theme?.colors?.primary600 || "#4945ff"};` : `border-color: ${theme?.colors?.primary600 || "#4945ff"} !important;`} }
|
|
7652
7666
|
${/* Strapi 4's scrolled header is fixed to the viewport's right edge: beside the pane in split mode, gone under it otherwise. */
|
|
7653
7667
|
resizable ? "[data-strapi-header-sticky] { right: var(--bp-pane) !important; }" : "[data-strapi-header-sticky] { display: none !important; }"}` }),
|
|
7654
7668
|
resizable && edge && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
@@ -8865,8 +8879,8 @@ var cached = null;
|
|
|
8865
8879
|
var pending = null;
|
|
8866
8880
|
var flight = null;
|
|
8867
8881
|
var load = (get) => flight || (flight = get("/blockscene/catalog").then(({ data }) => {
|
|
8868
|
-
cached = data;
|
|
8869
|
-
return
|
|
8882
|
+
cached = localizeBlocks(data, adminLocale());
|
|
8883
|
+
return cached;
|
|
8870
8884
|
}).finally(() => {
|
|
8871
8885
|
flight = null;
|
|
8872
8886
|
}));
|
package/dist/admin.mjs
CHANGED
|
@@ -405,6 +405,11 @@ function localized(value, locale = "en") {
|
|
|
405
405
|
const key = keys.find((k) => k.toLowerCase() === lower) ?? keys.find((k) => k.toLowerCase() === lang(lower)) ?? keys.find((k) => lang(k) === lang(lower)) ?? keys.find((k) => lang(k) === "en") ?? keys[0];
|
|
406
406
|
return key === void 0 ? void 0 : value[key];
|
|
407
407
|
}
|
|
408
|
+
function localizeBlocks(catalog, locale) {
|
|
409
|
+
if (!catalog?.components) return catalog;
|
|
410
|
+
const text = (value) => localized(value, locale) ?? void 0;
|
|
411
|
+
return { ...catalog, components: Object.fromEntries(Object.entries(catalog.components).map(([uid, entry]) => [uid, entry && (typeof entry.label === "object" || typeof entry.description === "object") ? { ...entry, label: text(entry.label), description: text(entry.description) } : entry])) };
|
|
412
|
+
}
|
|
408
413
|
function fieldText(fields, owner, name, label, locale, on) {
|
|
409
414
|
const entry = owner && fields?.[owner]?.[name] || {};
|
|
410
415
|
const out = {};
|
|
@@ -412,7 +417,7 @@ function fieldText(fields, owner, name, label, locale, on) {
|
|
|
412
417
|
if (text !== void 0) out.label = text;
|
|
413
418
|
for (const key of ["description", "placeholder", "help"]) {
|
|
414
419
|
const value = localized(entry[key], locale);
|
|
415
|
-
if (value
|
|
420
|
+
if (value != null) out[key] = value;
|
|
416
421
|
}
|
|
417
422
|
return Object.keys(out).length ? out : null;
|
|
418
423
|
}
|
|
@@ -6240,7 +6245,7 @@ function ZoneGallery({
|
|
|
6240
6245
|
setConfig(null);
|
|
6241
6246
|
setDetail(null);
|
|
6242
6247
|
get("/blockscene/catalog").then(({ data }) => {
|
|
6243
|
-
if (active) setConfig(data);
|
|
6248
|
+
if (active) setConfig(localizeBlocks(data, t.locale));
|
|
6244
6249
|
}).catch(() => {
|
|
6245
6250
|
if (active) setError(t.failed);
|
|
6246
6251
|
});
|
|
@@ -6923,6 +6928,14 @@ var EdgeButton = styled6.button`
|
|
|
6923
6928
|
`;
|
|
6924
6929
|
var HOVER_ATTR = "data-blockscene-hover";
|
|
6925
6930
|
var HOVER_GRACE = 1200;
|
|
6931
|
+
var accordionCard = (row, ds) => {
|
|
6932
|
+
const toggle = row.querySelector("button[aria-expanded]");
|
|
6933
|
+
if (!toggle) return row;
|
|
6934
|
+
if (ds === 2) return toggle.closest("div[data-state]:has(> h3)") || row;
|
|
6935
|
+
for (let el = toggle.parentElement; el && el !== row; el = el.parentElement)
|
|
6936
|
+
if (getComputedStyle(el).borderTopStyle !== "none") return el;
|
|
6937
|
+
return row;
|
|
6938
|
+
};
|
|
6926
6939
|
var Frame2 = styled6.iframe`
|
|
6927
6940
|
position: absolute;
|
|
6928
6941
|
top: 0;
|
|
@@ -7588,7 +7601,8 @@ function PagePreview({
|
|
|
7588
7601
|
const item = hovered && active ? findZoneList(live.current.zoneLabel)?.querySelectorAll(":scope > li")[indexOf(hovered)] : void 0;
|
|
7589
7602
|
hoverRow.current = item || null;
|
|
7590
7603
|
if (!item) return setEdge(null);
|
|
7591
|
-
item
|
|
7604
|
+
const card = accordionCard(item, host.ds);
|
|
7605
|
+
card.setAttribute(HOVER_ATTR, "");
|
|
7592
7606
|
const measure = () => {
|
|
7593
7607
|
const r = item.getBoundingClientRect();
|
|
7594
7608
|
setEdge(r.bottom < 0 ? "up" : r.top > window.innerHeight ? "down" : null);
|
|
@@ -7597,7 +7611,7 @@ function PagePreview({
|
|
|
7597
7611
|
window.addEventListener("scroll", measure, true);
|
|
7598
7612
|
window.addEventListener("resize", measure);
|
|
7599
7613
|
return () => {
|
|
7600
|
-
|
|
7614
|
+
card.removeAttribute(HOVER_ATTR);
|
|
7601
7615
|
window.removeEventListener("scroll", measure, true);
|
|
7602
7616
|
window.removeEventListener("resize", measure);
|
|
7603
7617
|
};
|
|
@@ -7666,7 +7680,7 @@ function PagePreview({
|
|
|
7666
7680
|
},
|
|
7667
7681
|
children: [
|
|
7668
7682
|
/* @__PURE__ */ jsx9("style", { children: `${SPLIT_STYLE}
|
|
7669
|
-
[${HOVER_ATTR}] {
|
|
7683
|
+
[${HOVER_ATTR}] { ${host.ds === 2 ? `box-shadow: 0 0 0 1px ${theme?.colors?.primary600 || "#4945ff"};` : `border-color: ${theme?.colors?.primary600 || "#4945ff"} !important;`} }
|
|
7670
7684
|
${/* Strapi 4's scrolled header is fixed to the viewport's right edge: beside the pane in split mode, gone under it otherwise. */
|
|
7671
7685
|
resizable ? "[data-strapi-header-sticky] { right: var(--bp-pane) !important; }" : "[data-strapi-header-sticky] { display: none !important; }"}` }),
|
|
7672
7686
|
resizable && edge && /* @__PURE__ */ jsx9(
|
|
@@ -8883,8 +8897,8 @@ var cached = null;
|
|
|
8883
8897
|
var pending = null;
|
|
8884
8898
|
var flight = null;
|
|
8885
8899
|
var load = (get) => flight || (flight = get("/blockscene/catalog").then(({ data }) => {
|
|
8886
|
-
cached = data;
|
|
8887
|
-
return
|
|
8900
|
+
cached = localizeBlocks(data, adminLocale());
|
|
8901
|
+
return cached;
|
|
8888
8902
|
}).finally(() => {
|
|
8889
8903
|
flight = null;
|
|
8890
8904
|
}));
|
package/dist/server.cjs
CHANGED
|
@@ -302,6 +302,7 @@ var require_settings = __commonJS({
|
|
|
302
302
|
}
|
|
303
303
|
for (const [uid, schema] of Object.entries(config.schemas || {})) {
|
|
304
304
|
const entry = entries[uid] ||= {};
|
|
305
|
+
for (const [key, value] of Object.entries(config.blocks?.[uid] || {})) if (entry[key] === void 0) entry[key] = value;
|
|
305
306
|
entry.facets = facetsOf(uid, schema, config.schemas);
|
|
306
307
|
entry.typology ||= guessTypology(uid, schema?.info?.displayName);
|
|
307
308
|
entry.tags ||= [];
|
|
@@ -473,6 +474,70 @@ var require_settings = __commonJS({
|
|
|
473
474
|
}
|
|
474
475
|
return out;
|
|
475
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
|
+
}
|
|
476
541
|
var VARIANT = { id: /^[a-z0-9][a-z0-9_-]{0,39}$/, perComponent: 12, bytes: 16 * 1024, total: 256 * 1024, items: 100 };
|
|
477
542
|
var text = (value) => typeof value === "string";
|
|
478
543
|
var finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
@@ -566,7 +631,7 @@ var require_settings = __commonJS({
|
|
|
566
631
|
return out;
|
|
567
632
|
}
|
|
568
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) : []]));
|
|
569
|
-
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, 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 };
|
|
570
635
|
}
|
|
571
636
|
});
|
|
572
637
|
|
|
@@ -594,6 +659,22 @@ var require_hidden = __commonJS({
|
|
|
594
659
|
}
|
|
595
660
|
return { added, skipped };
|
|
596
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
|
+
}
|
|
597
678
|
function stripHidden(data, schema, schemaOf, name, depth = 0) {
|
|
598
679
|
if (!data || typeof data !== "object" || !schema || depth > 20) return data;
|
|
599
680
|
if (Array.isArray(data)) {
|
|
@@ -652,7 +733,7 @@ var require_hidden = __commonJS({
|
|
|
652
733
|
}
|
|
653
734
|
}));
|
|
654
735
|
}
|
|
655
|
-
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 };
|
|
656
737
|
}
|
|
657
738
|
});
|
|
658
739
|
|
|
@@ -1604,12 +1685,13 @@ var require_history = __commonJS({
|
|
|
1604
1685
|
});
|
|
1605
1686
|
|
|
1606
1687
|
// server/index.js
|
|
1607
|
-
var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
|
|
1688
|
+
var { PLUGIN, TEMPLATES, TYPOLOGIES, DEFAULTS, catalog, validateSettings, validateFields, validateVariants, schemaMetadata, mergeSaved, layer, overrides, validatePrefs, mergePrefs, safeUrl, fail } = require_settings();
|
|
1608
1689
|
var { safeGroups, validateGroups, layoutFields } = require_groups();
|
|
1609
|
-
var { hiddenName, injectHidden, registerHiddenStrip } = require_hidden();
|
|
1690
|
+
var { hiddenName, injectHidden, hideFromBuilder, registerHiddenStrip } = require_hidden();
|
|
1610
1691
|
var { CRON, covers, eventContentType, historyService, historyController, registerHistory, registerPurge } = require_history();
|
|
1611
1692
|
var { trashContentType, trashService, trashController } = require_trash();
|
|
1612
1693
|
var STRAPI5 = false;
|
|
1694
|
+
var withHidden = [];
|
|
1613
1695
|
var store = (strapi) => strapi.store({ type: "plugin", name: PLUGIN });
|
|
1614
1696
|
var LEGACY_PLUGIN = "block-picker";
|
|
1615
1697
|
async function readSettings(strapi) {
|
|
@@ -1730,6 +1812,9 @@ module.exports = {
|
|
|
1730
1812
|
settings: null,
|
|
1731
1813
|
// Edit view field texts from code (never stored): { uid: { attribute: { label, description, placeholder } } }. See README "Field labels".
|
|
1732
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,
|
|
1733
1818
|
// "Hide on the site": boolean attribute added to every Dynamic Zone component (a DB column); false adds nothing.
|
|
1734
1819
|
hiddenAttribute: "bsHidden"
|
|
1735
1820
|
},
|
|
@@ -1739,9 +1824,11 @@ module.exports = {
|
|
|
1739
1824
|
register({ strapi }) {
|
|
1740
1825
|
const plugin = strapi.plugin(PLUGIN);
|
|
1741
1826
|
const name = hiddenName(plugin.config("hiddenAttribute"));
|
|
1827
|
+
withHidden = [];
|
|
1742
1828
|
if (name === null) strapi.log.warn(`[${PLUGIN}] "hiddenAttribute" config ignored: expected a name like "bsHidden" or false. Hiding blocks is off.`);
|
|
1743
1829
|
if (!name) return;
|
|
1744
|
-
const { skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
|
|
1830
|
+
const { added, skipped } = injectHidden(strapi.components, strapi.contentTypes, name);
|
|
1831
|
+
withHidden = added;
|
|
1745
1832
|
if (skipped.length) strapi.log.warn(`[${PLUGIN}] "${name}" already exists with another type on ${skipped.join(", ")}; those blocks cannot be hidden.`);
|
|
1746
1833
|
},
|
|
1747
1834
|
async bootstrap({ strapi }) {
|
|
@@ -1761,6 +1848,7 @@ module.exports = {
|
|
|
1761
1848
|
strapi.plugin(PLUGIN).service("settings").variants();
|
|
1762
1849
|
registerPublishGuard(strapi);
|
|
1763
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")));
|
|
1764
1852
|
if (STRAPI5) {
|
|
1765
1853
|
registerHistory(strapi);
|
|
1766
1854
|
registerPurge(strapi);
|
|
@@ -1784,18 +1872,28 @@ module.exports = {
|
|
|
1784
1872
|
}
|
|
1785
1873
|
return project;
|
|
1786
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
|
+
};
|
|
1787
1883
|
let fields;
|
|
1788
1884
|
const fieldTexts = () => {
|
|
1789
1885
|
if (fields !== void 0) return fields;
|
|
1790
1886
|
const schemas = Object.fromEntries(Object.entries({ ...strapi.contentTypes, ...strapi.components }).map(([uid, schema]) => [uid, Object.keys(schema?.attributes || {})]));
|
|
1791
1887
|
const stale = [];
|
|
1888
|
+
let code = {};
|
|
1792
1889
|
try {
|
|
1793
|
-
|
|
1890
|
+
code = validateFields(strapi.plugin(PLUGIN).config("fields"), schemas, stale);
|
|
1794
1891
|
} catch (error) {
|
|
1795
|
-
fields
|
|
1796
|
-
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.`);
|
|
1797
1893
|
}
|
|
1798
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 };
|
|
1799
1897
|
return fields;
|
|
1800
1898
|
};
|
|
1801
1899
|
let variants;
|
|
@@ -1811,6 +1909,7 @@ module.exports = {
|
|
|
1811
1909
|
projectDefaults,
|
|
1812
1910
|
get,
|
|
1813
1911
|
fields: fieldTexts,
|
|
1912
|
+
schema: schemaMeta,
|
|
1814
1913
|
variants: insertVariants,
|
|
1815
1914
|
async set(value) {
|
|
1816
1915
|
const next = validateSettings(value, componentUids(strapi), contentTypeUids(strapi), apiUids(strapi), strapi.components);
|
|
@@ -1840,7 +1939,8 @@ module.exports = {
|
|
|
1840
1939
|
blockPreview: plugin.config("blockPreview"),
|
|
1841
1940
|
groups: plugin.config("groups"),
|
|
1842
1941
|
componentUids: componentUids(strapi),
|
|
1843
|
-
schemas: strapi.components
|
|
1942
|
+
schemas: strapi.components,
|
|
1943
|
+
blocks: plugin.service("settings").schema().blocks
|
|
1844
1944
|
});
|
|
1845
1945
|
const settings = await plugin.service("settings").get();
|
|
1846
1946
|
const media = await resolveMedia(strapi, settings);
|
|
@@ -1874,7 +1974,7 @@ module.exports = {
|
|
|
1874
1974
|
settings: ({ strapi }) => ({
|
|
1875
1975
|
async find(ctx) {
|
|
1876
1976
|
const settings = await strapi.plugin(PLUGIN).service("settings").get();
|
|
1877
|
-
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;
|
|
1878
1978
|
const components = Object.entries(strapi.components || {}).map(([uid, schema]) => ({
|
|
1879
1979
|
uid,
|
|
1880
1980
|
displayName: schema.info?.displayName || uid,
|
package/package.json
CHANGED