@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 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`.