@aduptive/strapi-blockscene 1.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +340 -0
- package/dist/admin.cjs +1838 -0
- package/dist/admin.mjs +1808 -0
- package/dist/server.cjs +419 -0
- package/package.json +67 -0
- package/strapi-admin.js +1 -0
- package/strapi-server.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrea Scarpello
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Blockscene for Strapi
|
|
2
|
+
|
|
3
|
+
A visual gallery for the components already allowed in a native Dynamic Zone,
|
|
4
|
+
with configurable thumbnails, SVG wireframes, editor preferences and, on
|
|
5
|
+
Strapi 5, a whole-page preview that edits the form through your own frontend.
|
|
6
|
+
No renderer, framework or content migration is required.
|
|
7
|
+
|
|
8
|
+
**Alpha.** Published as pre-releases; APIs, settings and the preview bridge may
|
|
9
|
+
still change between alphas. See [compatibility](docs/COMPATIBILITY.md).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
One npm package, two distributions selected by dist-tag:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# Strapi 5 (plugin major 2)
|
|
17
|
+
npm install @aduptive/strapi-blockscene@next
|
|
18
|
+
# Strapi 4 (plugin major 1)
|
|
19
|
+
npm install @aduptive/strapi-blockscene@strapi4
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Install only the matching distribution, then enable it in `config/plugins.js` (or
|
|
23
|
+
export the equivalent object in TypeScript):
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
module.exports = {
|
|
27
|
+
'blockscene': {
|
|
28
|
+
enabled: true,
|
|
29
|
+
config: {
|
|
30
|
+
previewBaseUrl: '/block-previews',
|
|
31
|
+
previewVersion: '2026-09-22', // optional cache buster appended as ?v=
|
|
32
|
+
components: {
|
|
33
|
+
'blocks.hero': {
|
|
34
|
+
label: 'Hero banner',
|
|
35
|
+
description: 'Introduce a page with a prominent title and image.',
|
|
36
|
+
category: 'Editorial',
|
|
37
|
+
keywords: 'heading introduction',
|
|
38
|
+
image: '/block-previews/hero.png',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Rebuild/restart the admin. Open a content type with a Dynamic Zone. The
|
|
47
|
+
editor panel provides one gallery button per editable, non-full zone plus
|
|
48
|
+
"Open all blocks" / "Close all blocks" for zones that already have blocks.
|
|
49
|
+
|
|
50
|
+
## Gallery
|
|
51
|
+
|
|
52
|
+
The modal has one header row: block count (`34 blocks`, or `3 of 34` while
|
|
53
|
+
filtering), search, a category filter (only when the zone spans more than one
|
|
54
|
+
category, with counts), a "Fields" switch that shows each block's attribute
|
|
55
|
+
list on its card, and a columns slider (1 to 5, remembered per browser). Blocks
|
|
56
|
+
are grouped by category, system-like categories (`admin`) last; search and the
|
|
57
|
+
category filter combine, and the empty state offers a reset. Categories come
|
|
58
|
+
from `config.components[uid].category`, else the component's `category` in its
|
|
59
|
+
schema. The modal is 80vw wide.
|
|
60
|
+
|
|
61
|
+
The native "Add a component to <zone>" button of an editable zone opens this
|
|
62
|
+
gallery as well (the click is intercepted in the capture phase; Strapi's
|
|
63
|
+
category picker never opens). Zones without the gallery, or with the editor
|
|
64
|
+
enhancements off, keep the native picker. The button is recognised by its
|
|
65
|
+
label ending with the raw zone name, which Strapi's message carries in every
|
|
66
|
+
locale; a zone whose name ends another zone's name (`blocks` and `sub blocks`)
|
|
67
|
+
is the documented limit.
|
|
68
|
+
|
|
69
|
+
## Thumbnail priority
|
|
70
|
+
|
|
71
|
+
Each card tries these sources in order and moves on when one fails to load.
|
|
72
|
+
A failure never blocks selecting the block.
|
|
73
|
+
|
|
74
|
+
1. **Custom image** chosen in Settings, Blockscene (Media Library file).
|
|
75
|
+
2. **Automatic image**: `components[uid].image` from the config, then
|
|
76
|
+
`<previewBaseUrl>/<uid>.webp` (with `?v=<previewVersion>` when set).
|
|
77
|
+
3. **Wireframe**: an inline SVG drawn with the configurable palette and the
|
|
78
|
+
template chosen per component (generic, banner, cards, image and text, FAQ),
|
|
79
|
+
or "No preview" (`none`) when no faithful image exists and a wireframe
|
|
80
|
+
would mislead. Without a template, generic is used. Layouts are never
|
|
81
|
+
guessed from the schema.
|
|
82
|
+
|
|
83
|
+
Generating new automatic images never overwrites a custom image. "Use automatic
|
|
84
|
+
image" only removes the override; the Media Library file is kept. If the chosen
|
|
85
|
+
file is deleted, the settings page warns and the next source is used.
|
|
86
|
+
|
|
87
|
+
## Settings page
|
|
88
|
+
|
|
89
|
+
The page keeps its title, change state ("Unsaved changes", "Saved", "No
|
|
90
|
+
unsaved changes") and Save button in a bar that floats over the content area
|
|
91
|
+
once the header scrolls out. Components are a responsive card grid. `PUT
|
|
92
|
+
/blockscene/settings` replaces the whole document; send the full object.
|
|
93
|
+
|
|
94
|
+
Settings, Blockscene. Stored in the plugin store; no rebuild or restart is
|
|
95
|
+
needed for the gallery to pick them up. Reading requires the
|
|
96
|
+
`Blockscene: Read gallery settings` permission and saving requires
|
|
97
|
+
`Change gallery settings`; anonymous or unauthorized requests are rejected.
|
|
98
|
+
|
|
99
|
+
- Wireframe palette (background, surfaces, text, accent) with a live preview
|
|
100
|
+
and "Restore default colors". Colors must be `#RRGGBB`.
|
|
101
|
+
- Component list with the effective source, choose/replace image from the
|
|
102
|
+
Media Library, "Use automatic image" and the wireframe template.
|
|
103
|
+
- Editor preferences: enhancements on/off, visibility of each collective
|
|
104
|
+
button, initial accordion state and the initial block mode.
|
|
105
|
+
|
|
106
|
+
### Editor preferences
|
|
107
|
+
|
|
108
|
+
| Option | Values | Default |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| Editor enhancements enabled | on/off | on |
|
|
111
|
+
| Show "Open all blocks" | yes/no | yes |
|
|
112
|
+
| Show "Close all blocks" | yes/no | yes |
|
|
113
|
+
| Initial accordion state | all closed / all open / remember | all closed |
|
|
114
|
+
| Preview route base URL (Strapi 5) | full URL or empty | empty: native Preview origin |
|
|
115
|
+
| Initial preview mode (Strapi 5) | form / side by side / preview | form |
|
|
116
|
+
|
|
117
|
+
The initial state is applied once per document/locale after the blocks
|
|
118
|
+
render; edits, reorders and newly inserted blocks are not re-applied. Hiding a
|
|
119
|
+
button only hides it; native accordion headers keep working.
|
|
120
|
+
|
|
121
|
+
"Remember" stores only the last explicit open all / close all click, as
|
|
122
|
+
`open`/`closed` in the browser's localStorage, keyed by admin URL, user id,
|
|
123
|
+
content type and zone. Another user in the same browser does not inherit it.
|
|
124
|
+
Blocked storage or an invalid value falls back to all closed. No sync between
|
|
125
|
+
devices.
|
|
126
|
+
|
|
127
|
+
The accordion controls drive the native Dynamic Zone accordions through their
|
|
128
|
+
`aria-expanded` headers, so no Strapi source is patched. They are validated on
|
|
129
|
+
the two supported versions only.
|
|
130
|
+
|
|
131
|
+
## Whole-page preview (Strapi 5)
|
|
132
|
+
|
|
133
|
+
The Strapi 5 distribution adds a "Page preview" panel with three modes: form,
|
|
134
|
+
side by side (resizable pane, native actions moved to a bar above the form) and
|
|
135
|
+
preview. The panel posts the live values of the first Dynamic Zone to a page
|
|
136
|
+
your frontend serves; the page renders them with its own components and styles
|
|
137
|
+
and can ask the admin to open a block, edit a field or pick media. Nothing is
|
|
138
|
+
saved or published by the preview.
|
|
139
|
+
|
|
140
|
+
Minimal configuration:
|
|
141
|
+
|
|
142
|
+
1. Serve a page that implements the bridge. `examples/page-preview/index.html`
|
|
143
|
+
is a framework-free reference: it answers `ready`/`ping`, renders
|
|
144
|
+
`update-page`, and sends `select`, `focus`, `edit`, `media`, `media-remove`.
|
|
145
|
+
Keep the origin check (`admin` query parameter or your own constant).
|
|
146
|
+
2. Allow the admin origin to embed it (`Content-Security-Policy: frame-ancestors`)
|
|
147
|
+
and allow the page origin in the Strapi admin CSP (`frame-src`).
|
|
148
|
+
3. Set the full route URL in Settings, Blockscene ("Preview route base URL"),
|
|
149
|
+
or leave it empty to reuse the origin of Strapi's native Preview (when
|
|
150
|
+
configured for the content type) with `/block-preview/page` appended.
|
|
151
|
+
Use the same host name the frontend dev server was started for
|
|
152
|
+
(`localhost` vs `127.0.0.1`): Next 16 refuses its dev WebSocket for the
|
|
153
|
+
other one and the page then never hydrates, so `ready` never arrives and
|
|
154
|
+
the panel reports the preview as unavailable with no error in either console.
|
|
155
|
+
|
|
156
|
+
The bridge only exposes what the schema allows: attributes marked `private`
|
|
157
|
+
are never projected, focused or edited; media URLs are passed through for any
|
|
158
|
+
http(s) host (S3/CDN providers included) and same-origin paths; the zone shown
|
|
159
|
+
is the first Dynamic Zone the user may read, and editing additionally needs the
|
|
160
|
+
update permission on it. Pending dialogs are dropped when the document or
|
|
161
|
+
locale changes.
|
|
162
|
+
|
|
163
|
+
Editing from the page: plain-text areas the page explicitly maps
|
|
164
|
+
(`data-block-field` + `contenteditable="plaintext-only"`) send `edit`; any other
|
|
165
|
+
editable field opens an admin modal with the field's own native input (custom
|
|
166
|
+
fields such as CKEditor keep their options; string/text use the admin input).
|
|
167
|
+
Apply updates the form and the preview; Cancel discards only the modal. Media
|
|
168
|
+
fields open the native Media Library. Everything is validated against the schema
|
|
169
|
+
and the zone's edit permission; a published page never receives unsaved values
|
|
170
|
+
by itself. Strapi 4 does not have this panel yet (see `docs/BACKLOG.md`).
|
|
171
|
+
|
|
172
|
+
## Layout groups (optional)
|
|
173
|
+
|
|
174
|
+
Some sites keep "wrappers" in the flat Dynamic Zone: an OPEN component, the
|
|
175
|
+
blocks it wraps, then a CLOSE marker component. The plugin has no built-in idea
|
|
176
|
+
of such components: with no configuration every component is an ordinary block,
|
|
177
|
+
and a wrapper-like component without a renderer on your preview page gets the
|
|
178
|
+
same localized "no preview renderer" fallback as any other block, with its
|
|
179
|
+
native form untouched. Opt in per site by mapping each OPEN uid to its CLOSE uid
|
|
180
|
+
in the plugin config (code config, validated at boot; there is no UI for it):
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
// config/plugins.js
|
|
184
|
+
'blockscene': { config: { groups: { 'wrappers.join': 'wrappers.close', 'wrappers.background': 'wrappers.close' } } }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Rules: keys and values must be existing component uids, an OPEN cannot be its
|
|
188
|
+
own CLOSE, and a CLOSE cannot also be an OPEN. Anything malformed is ignored
|
|
189
|
+
with a warning in the server log and the site falls back to ordinary blocks;
|
|
190
|
+
pairs naming unknown components are dropped. Several OPENs may share one CLOSE.
|
|
191
|
+
|
|
192
|
+
With a valid map:
|
|
193
|
+
|
|
194
|
+
- The gallery (form mode) and the page preview insert OPEN and its CLOSE in the
|
|
195
|
+
same unsaved change: an empty group, never a lone marker. When the CLOSE is
|
|
196
|
+
not allowed in that zone, or the zone has no room for two rows, nothing is
|
|
197
|
+
inserted and a notice explains why (two form dispatches in one React batch;
|
|
198
|
+
no partial state is rendered or saved). Children are added
|
|
199
|
+
through the group's own insertion seam; "+ Group" appears in top-level seams
|
|
200
|
+
of the preview page (reference implementation in `examples/page-preview`).
|
|
201
|
+
- Move up/down and remove act on the whole OPEN…CLOSE range, keeping child
|
|
202
|
+
order, ids and unsaved values. The native per-row controls are untouched.
|
|
203
|
+
- Structural policy: an OPEN starts a group that ends at its configured CLOSE;
|
|
204
|
+
groups may nest; a CLOSE right after its OPEN is a valid empty group. Errors
|
|
205
|
+
are `closeBeforeOpen` (CLOSE with no open group), `mismatch` (CLOSE is not the
|
|
206
|
+
one expected by the innermost open group) and `unclosed` (OPEN never closed).
|
|
207
|
+
- Nothing is repaired on read: the panel lists the problems (row, marker) and
|
|
208
|
+
the stray rows stay visible so the editor can fix them. Drafts save normally.
|
|
209
|
+
- Publishing is refused server-side while a configured pair is unbalanced.
|
|
210
|
+
Strapi 5: a document-service middleware intercepts every facade path that
|
|
211
|
+
publishes: `publish`, and `create`/`update` with `status: 'published'` (the
|
|
212
|
+
repository then runs its internal publish, which never re-enters the
|
|
213
|
+
facade, so it is caught at the facade call). Admin Publish, bulk publish,
|
|
214
|
+
REST and custom `strapi.documents` code pass through it. The rows checked are
|
|
215
|
+
the ones the request publishes: the data being written, or the stored draft
|
|
216
|
+
of the exact target locale (requested locale; every locale for `'*'`; the
|
|
217
|
+
default locale when none is given, as the repository resolves it). The check
|
|
218
|
+
runs before the repository's own work, not inside its transaction: a draft
|
|
219
|
+
written by a concurrent request between the check and the publish is not
|
|
220
|
+
covered, and neither are direct `strapi.db.query` writes. Strapi 4: database
|
|
221
|
+
lifecycles (`beforeCreate`/`beforeUpdate` with `publishedAt`,
|
|
222
|
+
`beforeUpdateMany` for bulk publish). The error is a 400 ValidationError whose
|
|
223
|
+
`details.errors[]` carry Strapi's `{ path, message }` shape (the admin shows
|
|
224
|
+
them on the rows) plus `code`, `uid`, `expected`. Documents without Dynamic
|
|
225
|
+
Zones and ordinary blocks are never affected. Verified in the local labs with
|
|
226
|
+
the admin API (publish, bulk publish, create with published status).
|
|
227
|
+
|
|
228
|
+
Configured groups are stored exactly as before: the flat list of components in
|
|
229
|
+
the zone. Removing the config only removes the group tools and the guard.
|
|
230
|
+
|
|
231
|
+
## Languages
|
|
232
|
+
|
|
233
|
+
All plugin chrome (gallery, settings, page preview, editor dialogs, diagnostics)
|
|
234
|
+
follows the admin user's UI locale through Strapi's `registerTrads`, with stable
|
|
235
|
+
message ids (`blockscene.<key>`) and English as the fallback for any missing
|
|
236
|
+
key or locale. Shipped catalogues: en, pt-BR, pt, fr, es, de, it, nl (same
|
|
237
|
+
files for Strapi 4 and 5). Every other locale the admin offers shows English
|
|
238
|
+
for the plugin's texts. The reference preview page receives the same locale in
|
|
239
|
+
`update-page` (`locale`) so its editor chrome can follow it; page content and
|
|
240
|
+
component display names are never translated by the plugin.
|
|
241
|
+
|
|
242
|
+
## Turning the editor enhancements off
|
|
243
|
+
|
|
244
|
+
- **From the panel**: switch "Editor enhancements enabled" off. The gallery
|
|
245
|
+
button, controls and initial-state logic disappear and the native picker and
|
|
246
|
+
accordions remain. Content, config, media and unsaved edits are preserved; the
|
|
247
|
+
form is not reloaded. The settings page stays available to turn it back on.
|
|
248
|
+
- **Emergency, outside the panel**: start the server with
|
|
249
|
+
`BLOCKSCENE_DISABLED=true` (or set `config.disabled: true`). It is read at
|
|
250
|
+
boot and reported by the catalog, wins over the saved settings and cannot be
|
|
251
|
+
undone from the client. Changing it requires a restart.
|
|
252
|
+
- **If the catalog request fails**, the panel renders nothing and the native
|
|
253
|
+
editor is used, so a plugin outage does not block editing.
|
|
254
|
+
- **Completely**: set `'blockscene': { enabled: false }` in `config/plugins.js`
|
|
255
|
+
and rebuild/restart. This is the only way out of an import/build failure; a
|
|
256
|
+
runtime toggle cannot recover an admin that fails to load. Native content is
|
|
257
|
+
intact after uninstalling.
|
|
258
|
+
|
|
259
|
+
## Automatic images: local capture command
|
|
260
|
+
|
|
261
|
+
Development-only tool; Playwright never runs on the Strapi server or in the
|
|
262
|
+
admin bundle. Your project exposes one page per block with a capturable root
|
|
263
|
+
element; any framework or static HTML works. See `examples/static-preview/`.
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
node scripts/capture-previews.mjs --manifest examples/static-preview/manifest.json \
|
|
267
|
+
--out /path/to/strapi/public/block-previews [--viewport 1280x720] [--timeout 15000] [--only blocks.hero]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Manifest contract:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"baseUrl": "http://127.0.0.1:3000",
|
|
275
|
+
"selector": "[data-block-preview]",
|
|
276
|
+
"blocks": { "blocks.hero": { "url": "/preview/blocks.hero", "selector": "[data-block-preview]" } }
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Without `baseUrl`, relative URLs resolve next to the manifest file. The page
|
|
281
|
+
sets `data-preview-ready="true"` on the root element (or `<html>`) when it has
|
|
282
|
+
finished rendering; the command also waits for fonts and images inside the
|
|
283
|
+
element, disables animations, captures only the element and encodes WebP in
|
|
284
|
+
the browser. Each capture is written to a temporary file and renamed, so a
|
|
285
|
+
timeout or error keeps the previous file and is listed in
|
|
286
|
+
`capture-report.json`. Files not produced by the command are untouched.
|
|
287
|
+
|
|
288
|
+
Demo data belongs to those pages, never to the insertion payload: clicking a
|
|
289
|
+
card inserts schema defaults only. Set `previewVersion` after regenerating so
|
|
290
|
+
browsers fetch the new files. Deploy the folder with the project or copy it to
|
|
291
|
+
the configured static base.
|
|
292
|
+
|
|
293
|
+
## Guarantees and limits
|
|
294
|
+
|
|
295
|
+
Tested versions: Strapi 4.26.1 (Node 20) and 5.52.1 (Node 22) in the local
|
|
296
|
+
labs of this repository, and 5.23.5 and 5.31.0 in two real site projects. The
|
|
297
|
+
peer ranges (`>=5.23.0 <6`, `>=4.20.0 <5`) express what installs without
|
|
298
|
+
overrides, not a promise for every minor in between.
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
- Native component payloads and ordinary Strapi save/publish flow.
|
|
302
|
+
- Only configured allowed components; zone limits and edit permissions checked.
|
|
303
|
+
- v4 uses native component insertion. v5 builds schema defaults and nested row
|
|
304
|
+
keys because Strapi does not export the native default-form helper.
|
|
305
|
+
- Interface in the admin user's locale (8 catalogues, English fallback); theme tokens and keyboard-operable cards.
|
|
306
|
+
- Authenticated admin catalog endpoint exposes only supported metadata, resolved
|
|
307
|
+
override URLs, palette and editor preferences; never the whole config.
|
|
308
|
+
- Top-level Dynamic Zones only. Conditional fields are omitted conservatively.
|
|
309
|
+
- "Open all" / "Close all" drive the native accordions: opening is batched into
|
|
310
|
+
one render; closing yields a frame per block so the admin stays responsive.
|
|
311
|
+
On a page with about 34 blocks whose fields include CKEditor, closing still
|
|
312
|
+
takes a few seconds with pauses up to about 1.7 s: that is the editors being
|
|
313
|
+
torn down by the host, not the plugin's loop.
|
|
314
|
+
- No screenshot server, no hourly job, no in-editor rendered preview.
|
|
315
|
+
- Removing the plugin leaves native content intact; remove its config entry and
|
|
316
|
+
rebuild. No plugin data migration is needed.
|
|
317
|
+
|
|
318
|
+
Configuration edits require a server restart. Backend URL/prefix deployments
|
|
319
|
+
should set `previewBaseUrl` explicitly. HTTP(S) image hosts must also be allowed
|
|
320
|
+
by the consuming app's Content Security Policy. Media Library URLs are served as
|
|
321
|
+
stored (relative for the local provider), which works when the admin and the
|
|
322
|
+
uploads share an origin.
|
|
323
|
+
|
|
324
|
+
## Development
|
|
325
|
+
|
|
326
|
+
- `admin/`: shared gallery, settings page, wireframes and version-specific adapters.
|
|
327
|
+
- `server/`: settings store, validation, permissions, the catalog endpoint and the layout-group publish guard (`groups.js` is shared with the admin).
|
|
328
|
+
- `admin/translations/`: one flat catalogue per admin locale (English is the source of truth).
|
|
329
|
+
- `scripts/capture-previews.mjs`: local thumbnail capture.
|
|
330
|
+
- `packages/strapi4`, `packages/strapi5`: independently installable distributions.
|
|
331
|
+
- `tests/`: runnable regression checks.
|
|
332
|
+
- [Local integration tests](docs/LOCAL-TESTING.md).
|
|
333
|
+
- [Release checklist](docs/RELEASE.md).
|
|
334
|
+
|
|
335
|
+
This plugin does not require Image Optimization; both can be installed together.
|
|
336
|
+
|
|
337
|
+
## License
|
|
338
|
+
|
|
339
|
+
MIT, copyright 2026 Andrea Scarpello (personal project, authored outside any
|
|
340
|
+
employer's work). See `LICENSE`.
|