@aduptive/strapi-blockscene 2.0.0-alpha.6 → 2.0.0-alpha.8

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # Blockscene for Strapi
2
2
 
3
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.
4
+ with configurable thumbnails, SVG wireframes, editor preferences and a
5
+ whole-page preview that edits the form through your own frontend.
6
6
  No renderer, framework or content migration is required.
7
7
 
8
8
  **Alpha.** Published as pre-releases; APIs, settings and the preview bridge may
@@ -45,9 +45,43 @@ module.exports = {
45
45
  ```
46
46
 
47
47
  Rebuild/restart the admin. Open a content type with a Dynamic Zone. The
48
- editor panel provides one gallery button per editable, non-full zone. Zones
49
- that already have blocks get "Open all blocks" / "Close all blocks" icon
50
- buttons right beside the native zone label ("blocks (3)").
48
+ native "Add a component to <zone>" button of each editable, non-full zone opens
49
+ the gallery instead of Strapi's category picker; a full zone keeps its native
50
+ button.
51
+
52
+ ### Zone bar
53
+
54
+ Right beside the native zone label ("blocks (3)") each zone gets a short bar:
55
+
56
+ - **Expand all / Collapse all**: one button with text. It reads the rows' own
57
+ state: "Expand all" while any block of the zone is collapsed, "Collapse all"
58
+ when every block is open (a header opened or closed by hand counts too), and
59
+ exposes it as `aria-expanded`. Blocks open or close a few at a time around the
60
+ viewport first (long pages stay responsive). Rows of a folded layout group
61
+ are left alone (see [Groups in the form](#groups-in-the-form)). Options `showOpenAll` /
62
+ `showCloseAll`: with both on (the default) the button toggles; with only one
63
+ on it offers that action alone, disabled while there is nothing to do; with
64
+ both off there is no button.
65
+ - **Undo and Redo** (Strapi 5, first zone only): icon and text, the shortcut in
66
+ the tooltip. See [Undo and redo](#undo-and-redo).
67
+ - **Editing mode** (first zone only, when a preview route exists): a
68
+ menu with Fields, Fields + page and Visual editor. Without a preview route the
69
+ menu is not shown; Settings, Blockscene says why.
70
+ - **"…" (More actions)**: Select blocks, and "Paste N blocks" while the
71
+ clipboard holds blocks this zone allows (see [Row actions](#row-actions)).
72
+ No entries, no menu.
73
+
74
+ "Select blocks" swaps the bar for a selection bar: a select-all box with the
75
+ count ("2 selected"; indeterminate while only some are selected, a click then
76
+ selects none), Copy (disabled until a block is selected) and Done; Esc leaves
77
+ too. Each row gets a checkbox at the start of its header.
78
+
79
+ An empty zone has no label row: its bar (Paste, and on the first zone Undo,
80
+ Redo and the mode) sits right after its native add button.
81
+
82
+ The plugin adds no side panel of its own for any of this. The Blockscene side
83
+ panel appears only for what has no place in the form: version history (when
84
+ enabled) and layout group problems. With neither, there is no panel.
51
85
 
52
86
  ## Gallery
53
87
 
@@ -70,8 +104,9 @@ The modal is a block browser (80vw wide):
70
104
  under `prefers-reduced-motion: reduce`. The panel shows a large live preview
71
105
  with Fit / Mobile / Tablet / Desktop widths (scaled to fit, as in the page
72
106
  preview) and a compact strip: label, uid, description, badges, fields (on
73
- demand), star and Insert. Double click, "+" or Enter on a focused card
74
- inserts without it.
107
+ demand), the variant choice (see [Insert variants](#insert-variants)), star
108
+ and Insert. Double click, "+" or Enter on a focused card inserts without it
109
+ (the first variant, when the block has variants).
75
110
 
76
111
  The magnified preview shows the card's thumbnail at once and fades the live
77
112
  page in over it once it has loaded. Live source, first match wins:
@@ -79,14 +114,16 @@ page in over it once it has loaded. Live source, first match wins:
79
114
  1. **Block preview URL** (`editor.blockPreviewUrl`, Settings, both versions):
80
115
  a plain page per block in a plain iframe, no bridge. Placeholders, URL
81
116
  encoded: `{uid}`, `{name}` (after the dot), `{category}` (before the dot),
82
- `{variant}` (`default`) and `{locale}` (the entry's locale, empty when the
83
- type is not localized). Example:
117
+ `{variant}` (the chosen [insert variant](#insert-variants)'s id, else
118
+ `default`) and `{locale}` (the entry's locale, empty when the type is not
119
+ localized). Example:
84
120
  `http://localhost:3026/{locale}/block-preview/{name}/{variant}`. Such a
85
121
  fixtures route is usually dev-only: leave the option empty on production.
86
- 2. **Page preview route** (Strapi 5: the preview route base URL, else the
122
+ 2. **Page preview route** (the preview route base URL, else on Strapi 5 the
87
123
  native Preview origin + `/block-preview/page`): the same bridge as the
88
- whole-page preview, sent one block made from the schema defaults, mode
89
- `preview`. Read-only: every message except `ready` is ignored.
124
+ whole-page preview, sent one block made from the chosen variant over the
125
+ schema defaults, mode `preview`. Read-only: every message except `ready`
126
+ is ignored.
90
127
  3. Otherwise the image only.
91
128
 
92
129
  If the live page neither loads nor says `ready` within 8 s, the image stays
@@ -123,6 +160,59 @@ label ending with the raw zone name, which Strapi's message carries in every
123
160
  locale; a zone whose name ends another zone's name (`blocks` and `sub blocks`)
124
161
  is the documented limit.
125
162
 
163
+ ### Insert variants
164
+
165
+ A block can offer named presets of its field values, so the editor inserts
166
+ "Hero, dark" instead of the bare schema defaults. Variants come from the
167
+ project's code, never from the Settings page: the plugin config
168
+ `components[uid].variants`, a list of `{ id, label, values }`.
169
+
170
+ ```js
171
+ // config/plugins.js
172
+ components: {
173
+ 'blocks.hero': {
174
+ variants: [
175
+ { id: 'default', label: 'Default', values: {} },
176
+ { id: 'dark', label: { en: 'Dark', 'pt-BR': 'Escuro' }, values: {
177
+ title: 'Big launch', theme: 'dark',
178
+ link: { text: 'Read more', url: '/news' },
179
+ items: [{ label: 'One' }, { label: 'Two' }],
180
+ } },
181
+ // A frontend fixture works as is: ids, __component, media and relations are dropped.
182
+ { id: 'cases', label: 'Cases', values: require('../fixtures/hero/cases.json').data },
183
+ ],
184
+ },
185
+ },
186
+ ```
187
+
188
+ - **Choosing**: with two or more variants the magnified block shows them as a
189
+ row of buttons; the live preview follows the choice (`{variant}` in the
190
+ block preview URL, or the variant's block sent through the bridge) and
191
+ Insert inserts it. Double click, "+" and Enter insert the first variant.
192
+ The picker opened from a page preview insertion seam offers the same
193
+ choice. A block without variants behaves as before; Settings, Blockscene
194
+ lists each block's variants read-only.
195
+ - **Merge**: `values` is a partial field map over the schema defaults. A
196
+ nested component merges over its own defaults; a repeatable list replaces
197
+ the default list, each item over the item's defaults. The row goes through
198
+ the same form path as any gallery insert (Strapi 5 `addFieldRow`, Strapi 4
199
+ `addComponentToDynamicZone` then the fields' `onChange`), so nothing is
200
+ saved until Save and undo/redo covers it.
201
+ - **Supported values**, checked against the schema once at boot: text-like
202
+ fields (strings, rich text, email, uid, dates as strings), numbers
203
+ (integers, big integers, floats, decimals), booleans, enumerations (one of
204
+ their values), JSON, blocks (an array) and `null`; nested components,
205
+ single or repeatable (up to the attribute's `max`, and 100), each validated
206
+ against its own schema. Media and relation values are dropped with a boot
207
+ warning: their ids belong to one install and are not checked here. `id` and
208
+ `__component` keys are ignored, so a content-API shaped block works.
209
+ Unknown attributes, passwords and Dynamic Zones reject the variant.
210
+ - **Bounds**: `id` is 1 to 40 of `a-z 0-9 - _`, unique per block (it is the
211
+ `{variant}` URL segment); `label` a string of 1 to 60 characters or
212
+ `{ "<locale>": string }`; at most 12 variants per block, 16 KB of values
213
+ per variant and 256 KB for all of them. An invalid variant is left out with
214
+ a warning naming the problem; the block keeps its valid ones.
215
+
126
216
  ## Row thumbnails
127
217
 
128
218
  Each added block shows its thumbnail at the left of the native accordion
@@ -152,14 +242,14 @@ Each block header gets a few icons right before Strapi's own delete button
152
242
  nested components, new row key, media kept, relations kept (Strapi 5 reads
153
243
  the source row's relations from the server and adds its unsaved changes).
154
244
  A group OPEN duplicates with its whole range. Option `duplicate`.
155
- - **Copy / Paste** across pages. The copy icon copies one block; the select
156
- icon in the zone label shows a checkbox per row, then "Copy selected". The
245
+ - **Copy / Paste** across pages. The copy icon copies one block; "Select
246
+ blocks" in the zone bar's "…" menu shows a checkbox per row, then Copy. The
157
247
  blocks are stored in this browser's localStorage (`blockscene:clipboard:v1`,
158
- with the source content type and locale). "Paste" appears in the zone label
159
- (under the gallery button for an empty zone) and in the page preview seams.
160
- Paste is all or nothing: every block must be allowed in the target zone, fit
161
- its maximum, and groups must be whole; otherwise a notice explains why and the
162
- form is not changed. Media and relations are kept by id/documentId, so paste
248
+ with the source content type and locale). "Paste N blocks" appears in the
249
+ zone bar's "…" menu of every zone that allows all the copied components, and
250
+ in the page preview seams. Paste is all or nothing: every block must be
251
+ allowed in the target zone, fit its maximum, and groups must be whole;
252
+ otherwise a notice explains why and the form is not changed. Media and relations are kept by id/documentId, so paste
163
253
  within the same install. Option `clipboard`.
164
254
  - **Confirm delete**. The native delete asks "Delete block …?" first;
165
255
  confirming runs Strapi's own removal. On a group marker the dialog says only
@@ -219,15 +309,16 @@ needed for the gallery to pick them up. Reading requires the
219
309
  and "Restore default colors". Colors must be `#RRGGBB`.
220
310
  - Component list with the effective source, choose/replace image from the
221
311
  Media Library, "Use automatic image", the wireframe template, the gallery
222
- typology ("Automatic" shows the guess) and tags (comma separated).
312
+ typology ("Automatic" shows the guess) and tags (comma separated); a group
313
+ OPEN also has its layout grid fields (see [Layout grid](#layout-grid)).
223
314
  - Editor preferences: enhancements on/off, visibility of each collective
224
315
  button, initial accordion state and the initial block mode.
225
316
  - Version history (Strapi 5): module switch, covered content types, retention
226
317
  (see "Version history").
227
318
  - Content types: every project type with a Dynamic Zone, each with on/off
228
- (off leaves its edit view fully native) and, on Strapi 5, the mode its edit
229
- views open in ("Default" follows the global preview mode) and its visual
230
- editor sidebar (see below).
319
+ (off leaves its edit view fully native), the mode its edit
320
+ views open in ("Default" follows the global preview mode), its visual
321
+ editor sidebar and optionally its own page preview toolbar (see below).
231
322
  - "Restore project defaults" (or "Reset to defaults" when the project has no
232
323
  code defaults), after a confirmation, deletes what was saved here
233
324
  (`DELETE /blockscene/settings`, same permission as saving).
@@ -259,7 +350,12 @@ module.exports = {
259
350
  The effective settings are the built-in defaults, then the code `settings`,
260
351
  then what was saved on the page:
261
352
 
262
- - `palette` and `editor`: a saved key wins over the code key by key.
353
+ - `palette` and `editor`: a saved key wins over the code key by key, except
354
+ an empty `previewUrl` or `blockPreviewUrl`, which means "not set": the code
355
+ value applies. Clearing either field on the page therefore falls back to the
356
+ project default (there is no saved "off" for a URL the code provides; remove
357
+ it from the code settings, or set `previewMode` per content type, to keep a
358
+ type out of the page preview).
263
359
  - `components` and `contentTypes`: a saved entry replaces the code entry for
264
360
  that uid as a whole (a saved `api::page.page` entry replaces its code
265
361
  sidebar too); other uids keep their code values.
@@ -277,27 +373,31 @@ ignored (boot continues). `GET /blockscene/settings` returns them as
277
373
  | Option | Values | Default |
278
374
  | --- | --- | --- |
279
375
  | Editor enhancements enabled | on/off | on |
280
- | Show "Open all blocks" | yes/no | yes |
281
- | Show "Close all blocks" | yes/no | yes |
376
+ | Show "Expand all" (`showOpenAll`) | yes/no | yes |
377
+ | Show "Collapse all" (`showCloseAll`) | yes/no | yes |
282
378
  | Show block thumbnails in the rows (`showRowThumbnails`) | yes/no | yes |
283
379
  | Initial accordion state | all closed / all open / remember | all closed |
284
380
  | Load rich-text editors on demand (`lazyEditors`, Strapi 5) | yes/no | yes |
285
381
  | Custom fields loaded on demand (`lazyFields`, Strapi 5) | up to 20 uids `plugin::x.y` / `global::x` | `["plugin::ckeditor5.CKEditor"]` |
286
- | Preview route base URL (Strapi 5) | full URL or empty | empty: native Preview origin |
382
+ | Preview route base URL | full URL or empty | empty: native Preview origin (Strapi 5; none on Strapi 4) |
287
383
  | Block preview URL | http(s) URL with placeholders, up to 500 characters, or empty | empty: page preview route, else image |
288
- | Initial preview mode (Strapi 5) | form / side by side / preview | form |
384
+ | Initial preview mode | form / side by side / preview | form |
289
385
  | Confirm before deleting a block (`confirmDelete`) | yes/no | yes |
290
386
  | Duplicate on each block (`duplicate`) | yes/no | yes |
291
387
  | Copy and Paste (`clipboard`) | yes/no | yes |
292
388
  | Blocks hidden on the site (`hiddenBlocks`) | `strip` / `flag` / `off` | `strip` |
293
389
  | Friendly field labels (`friendlyLabels`, see [Field labels](#field-labels)) | yes/no | yes |
390
+ | Page preview toolbar (`previewToolbar`, see [Pane toolbar](#pane-toolbar)) | ordered list of `modes`, `history`, `devices`, `status`, `actions` | all five |
391
+ | Page widths (`previewDevices`) | 1 to 8 of `fit`, `mobile`, `tablet`, `desktop`, `{ label, width }` | the four built-in |
294
392
 
295
393
  Every edit view opens in its content type's mode, else the initial preview
296
394
  mode. Editors can switch while they edit; the switch is not remembered.
297
395
 
298
396
  The initial state is applied once per document/locale after the blocks
299
- render; edits, reorders and newly inserted blocks are not re-applied. Hiding a
300
- button only hides it; native accordion headers keep working.
397
+ render; edits, reorders and newly inserted blocks are not re-applied.
398
+ `showOpenAll` and `showCloseAll` shape the zone bar's single toggle (both: it
399
+ toggles; one: only that action; none: no button). Hiding it only hides it;
400
+ native accordion headers keep working.
301
401
 
302
402
  Lazy editors: a custom field listed in `lazyFields` first shows a read-only
303
403
  preview of its HTML (same label, hint and error; scripts, frames, styles, event
@@ -309,7 +409,7 @@ registered custom field at boot and passes the others straight through; until
309
409
  the catalog loads, and when the plugin or the content type is off, every field
310
410
  is native.
311
411
 
312
- "Remember" stores only the last explicit open all / close all click, as
412
+ "Remember" stores only the last explicit Expand all / Collapse all click, as
313
413
  `open`/`closed` in the browser's localStorage, keyed by admin URL, user id,
314
414
  content type and zone. Another user in the same browser does not inherit it.
315
415
  Blocked storage or an invalid value falls back to all closed. No sync between
@@ -319,11 +419,12 @@ The accordion controls drive the native Dynamic Zone accordions through their
319
419
  `aria-expanded` headers, so no Strapi source is patched. They are validated on
320
420
  the two supported versions only.
321
421
 
322
- ## Whole-page preview (Strapi 5)
422
+ ## Whole-page preview
323
423
 
324
- The Strapi 5 distribution adds a "Page preview" panel with three modes: form,
325
- side by side (resizable pane, native actions moved to a bar above the form) and
326
- preview. The panel posts the live values of the first Dynamic Zone to a page
424
+ Both distributions add a page preview with three modes, chosen from the
425
+ first zone's bar and from the pane toolbar: Fields (form), Fields + page (side
426
+ by side: resizable pane, native actions moved to a bar above the form) and
427
+ Visual editor. It posts the live values of the first Dynamic Zone to a page
327
428
  your frontend serves; the page renders them with its own components and styles
328
429
  and can ask the admin to open a block, edit a field or pick media. Nothing is
329
430
  saved or published by the preview.
@@ -393,7 +494,7 @@ Minimal configuration:
393
494
  Use the same host name the frontend dev server was started for
394
495
  (`localhost` vs `127.0.0.1`): Next 16 refuses its dev WebSocket for the
395
496
  other one and the page then never hydrates, so `ready` never arrives and
396
- the panel reports the preview as unavailable with no error in either console.
497
+ the pane reports the preview as unavailable with no error in either console.
397
498
 
398
499
  The bridge only exposes what the schema allows: attributes marked `private`
399
500
  are never projected, focused or edited; media URLs are passed through for any
@@ -411,15 +512,115 @@ page updates while you type; Done, Esc or the backdrop put the block back. Side
411
512
  by side focuses the field in the form on the left instead. Media
412
513
  fields open the native Media Library. Everything is validated against the schema
413
514
  and the zone's edit permission; a published page never receives unsaved values
414
- by itself. Strapi 4 does not have this panel yet (see `docs/BACKLOG.md`).
515
+ by itself.
516
+
517
+ On Strapi 4 (run on 4.26.1; the edit view APIs it uses are the same in 4.11.0)
518
+ the page preview is the same: the three modes, the same bridge
519
+ (`blockscene:page-preview:v1`, so one frontend route serves both), widths,
520
+ hover sync, the block dialog, the visual editor sidebar, custom panels,
521
+ insertion, groups and paste from the page, and the per content type settings.
522
+ It reads and writes the edit view's own data (`useCMEditViewDataManager`); new
523
+ blocks use the native insertion at a position. Differences:
524
+
525
+ - No undo and redo (no History buttons, no shortcuts). Strapi 4's form has no
526
+ way to set all values at once, and restoring each field would also undo the
527
+ relations it loads lazily, which Save would then disconnect.
528
+ - No native Preview to fall back on: set the preview route base URL.
529
+ - The toolbar's status shows Draft or Published (Strapi 4 has no Modified
530
+ state) and the unsaved hint; its Save and Publish (or Unpublish) click the
531
+ edit view header's own buttons, so their checks and dialogs are Strapi's.
532
+ - The browser smoke covers split mode, select, inline and live edits, widths,
533
+ hover, the block dialog, a sidebar item, media, insertion and toolbar Save
534
+ on 4.26.1; group tools, paste and custom panels from the page share the
535
+ Strapi 5 code and are checked there only.
536
+ - With Strapi 4's two navigation columns the content area is narrower: the
537
+ form keeps at least 520 px, so side by side the page gets less than half on
538
+ a 1440 px screen.
539
+
540
+ ### Pane toolbar
541
+
542
+ Which controls the toolbar shows, and in which order, is `previewToolbar`:
543
+ `modes` (Fields, Fields + page, Visual editor), `history` (Undo, Redo),
544
+ `devices` (the width menu), `status` (Draft/Modified/Published and the unsaved
545
+ hint) and `actions` (Save, Publish). A missing id is hidden; `status` and
546
+ `actions` stay one group on the right, placed where the first of them is
547
+ listed. The preview's loading or error notice always shows. Without `modes` the
548
+ pane stays in the mode the edit view opened in (for example a visual editor
549
+ only view with `previewMode: 'preview'`); the form's zone bar keeps its own
550
+ Undo, Redo and mode menu.
551
+
552
+ `previewDevices` lists the width menu's entries in order; the first is used
553
+ until an editor picks another. Besides the four names, `{ label, width }` adds
554
+ a custom width (label up to 24 characters, width a whole number from 240 to
555
+ 3840 px; no name or width twice; up to 8 entries). With a single entry the menu
556
+ is hidden.
557
+
558
+ ```js
559
+ // Settings, Blockscene (Editor preferences, or per content type), PUT /blockscene/settings, or the code `settings`:
560
+ editor: {
561
+ previewToolbar: ['modes', 'devices', 'actions'],
562
+ previewDevices: ['fit', 'mobile', { label: 'Laptop', width: 1280 }, 'desktop'],
563
+ },
564
+ contentTypes: {
565
+ 'api::landing.landing': { previewToolbar: ['actions'], previewDevices: ['desktop'] },
566
+ }
567
+ ```
568
+
569
+ A content type's value replaces the global one. The Settings page edits both as
570
+ checklists (check to show, arrows to order, "Add a width" for custom widths);
571
+ "Own page preview toolbar" under a content type starts as a copy of the global
572
+ choice, and turning it off goes back to the global one.
573
+
574
+ ### Custom sidebar panels
575
+
576
+ A project (in its `src/admin/app.tsx`) or another plugin can add its own panel
577
+ to the visual editor sidebar. Registered panels come after the configured field
578
+ items, in registration order; the rail shows in the Visual editor mode as soon
579
+ as there is one item.
580
+
581
+ ```tsx
582
+ // src/admin/app.tsx
583
+ const Notes = ({ values, onChange, disabled, close }) => (
584
+ <textarea value={values.notes ?? ''} disabled={disabled}
585
+ onChange={(e) => onChange('notes', e.target.value)} />
586
+ )
587
+ export default {
588
+ bootstrap(app) {
589
+ app.getPlugin('blockscene')?.apis.registerPanel({
590
+ id: 'notes', // 1 to 40 letters, digits, _ or -; the same id again replaces the panel
591
+ label: 'Notes', // 1 to 40 characters
592
+ icon: 'info', // optional: an icon name (the sidebar icons above, for example) or a React element
593
+ open: 'drawer', // 'drawer' (default) or 'modal'
594
+ contentTypes: ['api::page.page'], // optional: only these types (default: every type)
595
+ Component: Notes,
596
+ })
597
+ },
598
+ }
599
+ ```
600
+
601
+ `Component` receives `model` (content type uid), `documentId` (undefined while
602
+ creating), `locale`, `values` (the live form values), `onChange(name, value)`
603
+ (the form's own setter: the change is unsaved, goes through undo and redo, and
604
+ Save or Publish store it with the usual permission checks), `disabled` (the form
605
+ is read-only) and `close()`. It renders inside the Content Manager edit view, so
606
+ it can also use the admin's hooks (`useForm`, `useFetchClient`...). Strapi runs
607
+ every plugin's `register` before any `bootstrap`, then the host's `bootstrap`:
608
+ calling `registerPanel` from either bootstrap works. An invalid panel is logged
609
+ (`[blockscene] registerPanel: ...`) and ignored; `registerPanel` returns `true`
610
+ or `false`. A panel that throws while rendering shows the plugin's error notice
611
+ in its drawer; the editor keeps working. On Strapi 4 the same call works (its
612
+ `values` and `onChange` are the edit view data manager's); the lab smoke covers
613
+ panels on Strapi 5 only.
415
614
 
416
615
  ### Undo and redo
417
616
 
418
- The Blockscene panel and the pane toolbar have Undo and Redo buttons (Strapi 5).
617
+ The first zone's bar and the pane toolbar have Undo and Redo buttons (Strapi 5),
618
+ on one shared history (the shortcuts use it too).
419
619
  They step through the whole edit view: blocks inserted, removed or moved from the
420
620
  gallery, the page, groups or the native actions, and any field edit. Changes
421
621
  within 400 ms of each other are one step, so a burst of typing undoes at once;
422
- up to 100 steps. Shortcuts: Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z and Ctrl+Y, only while
622
+ a block dragged with the mouse is one step however long the drag lasts; up to
623
+ 100 steps. Shortcuts: Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z and Ctrl+Y, only while
423
624
  the focus is outside a text field, select or rich text editor (those keep their
424
625
  own undo).
425
626
 
@@ -467,7 +668,7 @@ With a valid map:
467
668
  groups may nest; a CLOSE right after its OPEN is a valid empty group. Errors
468
669
  are `closeBeforeOpen` (CLOSE with no open group), `mismatch` (CLOSE is not the
469
670
  one expected by the innermost open group) and `unclosed` (OPEN never closed).
470
- - Nothing is repaired on read: the panel lists the problems (row, marker) and
671
+ - Nothing is repaired on read: the Blockscene side panel lists the problems (row, marker) and
471
672
  the stray rows stay visible so the editor can fix them. Drafts save normally.
472
673
  - Publishing is refused server-side while a configured pair is unbalanced.
473
674
  Strapi 5: a document-service middleware intercepts every facade path that
@@ -491,6 +692,81 @@ With a valid map:
491
692
  Configured groups are stored exactly as before: the flat list of components in
492
693
  the zone. Removing the config only removes the group tools and the guard.
493
694
 
695
+ ### Groups in the form
696
+
697
+ The edit view draws the same structure over Strapi's native rows (Strapi 5 and
698
+ 4; data attributes on the rows, nothing of the Content Manager is patched):
699
+
700
+ - The rows between an OPEN and its CLOSE are indented under it with a guide line
701
+ in the admin's primary colour, one level (and one line) per nested group.
702
+ - The CLOSE row reads as the group's end: compact, dimmed, without a drag handle
703
+ (it cannot be dragged on its own; its delete stays, with the marker notice).
704
+ - Each closed group's OPEN header gets a chevron before its title. It folds the
705
+ group: its rows and CLOSE are hidden and the header shows "N blocks" (every
706
+ row inside, nested ones too, CLOSE markers excluded). Folding is kept in memory
707
+ for the session, per document, zone and group; a reload shows every group open.
708
+ A validation error inside a folded group (on Save or Publish) unfolds it.
709
+ Expand all / Collapse all act on the visible block accordions only: they never
710
+ open the forms of a folded group's rows, and a group keeps its own chevron.
711
+ - Moving an OPEN moves its group. Nothing intercepts Strapi's drag and drop:
712
+ after any change of the zone, if the only thing that changed is one OPEN
713
+ moving, its members as they were (children and CLOSE) are put back right after
714
+ it, in the same unsaved form change. This covers the mouse (settled on drop:
715
+ Strapi moves the row on every hover), the keyboard (Space, arrows, Space on the
716
+ drag handle) and the move arrows of small screens. An OPEN dropped among its
717
+ own rows (a single keyboard or arrow step down) moves the group one row down
718
+ instead. The whole drag and the correction are one undo step (Strapi 5).
719
+ - Everything else keeps the flat-list meaning: a child dragged out of the range
720
+ is an ordinary block from then on, a block dropped inside becomes a child, and
721
+ an OPEN dropped inside another group is nested there with its rows. A CLOSE
722
+ moved by other means (the small-screen arrows) ends the group where it lands.
723
+ Unclosed groups and stray markers are indented as the page preview shows them
724
+ but have no chevron and are never moved (the diagnostics list them).
725
+
726
+ ### Layout grid
727
+
728
+ An OPEN whose component lays its children out in columns (a "grid columns"
729
+ wrapper) can show them that way in the form. Name the OPEN attribute that holds
730
+ its desktop column count, per component, in Settings (the OPEN's card, "Layout
731
+ grid") or in the code `settings`:
732
+
733
+ ```json
734
+ { "components": { "wrappers.grid-columns": { "layout": { "columnsField": "columnsMd", "mobileColumnsField": "columns", "maxColumns": 3 } } } }
735
+ ```
736
+
737
+ - `columnsField` (required) and `mobileColumnsField` (optional) must be
738
+ attributes of that component that can hold a count: `integer`, `biginteger`,
739
+ `float`, `decimal`, `string`, or an `enumeration` whose every value is a whole
740
+ number (`["1", "2", "3"]`). `maxColumns` is 1 to 12 (default 12). Checked on
741
+ `PUT` and on the code settings at boot; a saved layout whose attribute was
742
+ removed is dropped on read. A layout on a component that is not a configured
743
+ OPEN does nothing.
744
+ - The count is read live from the OPEN's form: its value, else the attribute's
745
+ default, else 1, capped at `maxColumns` (a value that is not a whole number
746
+ from 1 counts as missing). The grid shows the desktop count; the mobile one is
747
+ named in its caption.
748
+ - Each child is a cell under the OPEN's header (its name and the first text of
749
+ its fields); a nested group is one cell ("N blocks"); the CLOSE stays a row
750
+ below. The children's own rows are hidden but stay mounted, so the form,
751
+ validation, Save and the row tools keep working on them; Expand all skips
752
+ them as it skips a folded group.
753
+ - A cell opens its block's native form in the block dialog (the one of the
754
+ visual editor), in any mode, with every field type. A validation error inside
755
+ a child gives its cell a red border; the dialog shows the field.
756
+ - Reorder by dragging a cell onto another, or with Alt+Arrow keys on a focused
757
+ cell (left/right one cell, up/down one row of the grid); nested groups move
758
+ whole. "Remove from group" in a cell's "…" menu moves the block right after
759
+ the CLOSE. "Add a block" opens the gallery (the zone's allowed components) and
760
+ inserts at the end of the group; an OPEN brings its CLOSE. Each of these is
761
+ one form change: one undo step on Strapi 5, nothing saved before Save.
762
+ - The icon on the OPEN's header switches that group between grid and list (the
763
+ native rows) for the session, per document and group. A folded group shows no
764
+ grid.
765
+ - Dragging a cell out of the grid onto the list is not supported (it would need
766
+ Strapi's own drag and drop): use "Remove from group", or the list view.
767
+
768
+ Strapi 5 and 4 (the block dialog is shared by both since the page preview core).
769
+
494
770
  ## Field labels
495
771
 
496
772
  The Content Manager shows each field under its "Configure the view" label, which
@@ -519,13 +795,23 @@ blockscene: { config: { fields: {
519
795
  pageSeo: { label: { en: 'Search engine settings', 'pt-BR': 'Configurações de SEO' },
520
796
  description: { en: 'Title and description shown on Google', 'pt-BR': 'Título e descrição no Google' } },
521
797
  },
522
- 'shared.seo': { metaTitle: { placeholder: 'Up to 60 characters' } },
798
+ 'shared.seo': { metaTitle: { placeholder: 'Up to 60 characters' },
799
+ metaDescription: { help: { en: 'Shown under the title in search results. Google cuts it around 155 characters; write for people, not keywords.' } } },
523
800
  } } },
524
801
  ```
525
802
 
526
803
  Keys are content type or component uids, then attribute names; each of
527
- `label` (up to 80 characters), `description` (300) and `placeholder` (120) is a
528
- string or `{ "<locale>": string }`. The map is validated at boot: an unknown
804
+ `label` (up to 80 characters), `description` (300), `placeholder` (120) and
805
+ `help` (500) is a string or `{ "<locale>": string }`.
806
+
807
+ `description` stays the line under the input. `help` is for the longer
808
+ explanation that would stretch the form: on Strapi 5 it becomes an "i" icon next
809
+ to the label, in the Content Manager's own label action slot (where the i18n
810
+ plugin puts its globe; both icons show on localized fields), with the text in a
811
+ tooltip on hover and on keyboard focus (the button is reachable with Tab and
812
+ named "More information"). A field can have both. On Strapi 4 the Content
813
+ Manager gives component fields, where blocks live, no label action, so `help`
814
+ is appended to the description under the input instead. The map is validated at boot: an unknown
529
815
  key, an empty or too long text, or a malformed locale code logs a warning
530
816
  naming it and the whole `fields` config is ignored (labels fall back to the two
531
817
  other steps). Entries for a uid or attribute the schema no longer has (a
@@ -613,7 +899,7 @@ harmless.
613
899
  ```js
614
900
  // config/plugins.js
615
901
  blockscene: { config: { settings: { history: { enabled: true, contentTypes: ['api::page.page', 'api::post.post'],
616
- retentionDays: 90, maxSnapshots: 100, eventDays: 365 } } } }
902
+ retentionDays: 90, maxSnapshots: 100, eventDays: 365, trashDays: 90 } } } }
617
903
  ```
618
904
 
619
905
  Events live in the `blockscene_events` table (content type
@@ -622,6 +908,100 @@ Builder). Removing the plugin, or installing a Blockscene version older than
622
908
  the one that added history, lets Strapi drop that table and every recorded
623
909
  version on the next start: back up `blockscene_events` first if you need it.
624
910
 
911
+ ### Trash (Strapi 5)
912
+
913
+ Part of the version history module: on with it, for the same content types.
914
+ Strapi has no trash in any plan; here a delete made through the document service
915
+ (the admin's Delete, bulk delete and "Delete locale", the APIs, plugins) also
916
+ writes one trash entry per document, in the same transaction as the delete.
917
+
918
+ **What an entry keeps.** Every deleted locale, draft and published rows (they
919
+ can differ), as snapshots like the history's; the document's title (its Content
920
+ Manager main field); who deleted it and when; and the links other documents and
921
+ blocks had to it. Strapi removes those links with the document, and the
922
+ document's own snapshot does not hold them when the relation is unidirectional
923
+ (declared only on the other side, like a menu listing pages or a block linking
924
+ to a page), so they are captured before the delete: owner, field, locale and the
925
+ position in the owner's list. A delete without a locale removes the default
926
+ locale only (Strapi's rule), `'*'` every locale; the entry has exactly what was
927
+ deleted. If the capture fails, nothing is deleted.
928
+
929
+ **Restore.** From the Trash page, which first shows what the restore would do
930
+ and writes nothing if something blocks it:
931
+
932
+ - Blocking: the content type is gone; a unique field (`unique: true` or a uid
933
+ field, such as a slug) is now used by another document in that locale; the
934
+ document has that locale again; a single type has a document again; none of
935
+ its locales is configured any more.
936
+ - Reported, then restored anyway: a published version existed (see below);
937
+ required fields are empty under the current schema (drafts may be, Strapi
938
+ checks them on publish); fields the schema no longer has, blocks whose
939
+ component the zone no longer allows, media files and related documents that no
940
+ longer exist (left out); locales no longer configured (skipped); links whose
941
+ owner is gone or now points elsewhere.
942
+
943
+ The document comes back as a **draft with its draft content**: the draft is the
944
+ latest work, and publishing is the editor's decision (the site may have moved
945
+ on). The published snapshot stays in the entry and in the preview. Every locale
946
+ is restored under one documentId, the default locale first, through the
947
+ document service (validation and the project's lifecycles run). A document that
948
+ is gone gets a new documentId, since Strapi's create does not take one; a locale
949
+ deleted from a document that still exists goes back into that document. The
950
+ captured links are put back where they were (same position in the owner's
951
+ list) when the owner still exists; links from published versions come back
952
+ when those owners are published again after this document is. A restore is one
953
+ transaction and is idempotent: the entry is claimed first, so a second restore
954
+ (a double click, another editor, another instance) returns the first one's
955
+ document. The history gets a `restore` event per locale.
956
+
957
+ **Delete forever** removes one entry (recorded as a `purge` event in the
958
+ activity). The nightly job deletes entries past their expiry, restored ones too,
959
+ in batches; neither ever deletes media files.
960
+
961
+ **Retention.** `trashDays` (90, 1 to 3650) in the same settings; each entry's
962
+ expiry is fixed when the document is deleted, so a later change applies to later
963
+ deletes.
964
+
965
+ ```js
966
+ blockscene: { config: { settings: { history: { enabled: true, trashDays: 30 } } } }
967
+ ```
968
+
969
+ Entries live in `blockscene_trash` (`plugin::blockscene.trash`, hidden from the
970
+ Content Manager and the Content-Type Builder); the same backup note as for
971
+ `blockscene_events` applies.
972
+
973
+ ### Activity and Trash pages (Strapi 5)
974
+
975
+ A main menu link, "Activity and trash", opens one page with a tab each (the
976
+ pages are tools for editors, not configuration, so they are not under
977
+ Settings). **Activity** lists every recorded event, newest first, 25 per page,
978
+ filtered by person, content type, action, date range and title (documents whose
979
+ main field contains the text now, or whose trash entry's title does); each row
980
+ links to the document's edit view while that locale exists and shows the
981
+ summary. **Trash** lists the entries (title, type, locales, who, when, when it
982
+ is deleted for good), filtered by content type, with restored entries on a
983
+ switch; Preview shows the fields and blocks of each deleted locale, read-only;
984
+ Restore shows the pre-check report and asks to confirm; Delete forever asks
985
+ first.
986
+
987
+ **Permissions.** `Read content activity` (`activity.read`), `Read the trash`
988
+ (`trash.read`), `Restore from the trash` (`trash.restore`), `Delete from the
989
+ trash forever` (`trash.purge`), none granted to non-super-admin roles by
990
+ default. On top of them, every list shows only content types the user may read
991
+ in the Content Manager (an entry of another type answers 404), restoring needs
992
+ create there and deleting forever needs delete. Admin API: `GET /blockscene/activity`
993
+ (`actor`, `contentType`, `action`, `from`, `to` as ISO dates, `q`, `page`,
994
+ `pageSize` up to 100), `GET /blockscene/trash` (`contentType`, `status`
995
+ trashed or restored, `page`, `pageSize`), `GET /blockscene/trash/:id` (the
996
+ preview), `GET /blockscene/trash/:id/check` (the pre-check),
997
+ `POST /blockscene/trash/:id/restore` (409 with the report when blocked) and
998
+ `DELETE /blockscene/trash/:id`. Lists never load snapshots.
999
+
1000
+ **Coverage boundary**, the same as the history's: the document service only.
1001
+ A raw `strapi.db.query` delete, SQL or a database restore leaves no trash
1002
+ entry; deleting a content type (Content-Type Builder) drops its data without
1003
+ one. The trash is a safety net for editors, not a backup: keep database backups.
1004
+
625
1005
  ## Languages
626
1006
 
627
1007
  All plugin chrome (gallery, settings, page preview, editor dialogs, diagnostics)
@@ -635,15 +1015,15 @@ component display names are never translated by the plugin.
635
1015
 
636
1016
  ## Turning the editor enhancements off
637
1017
 
638
- - **From the panel**: switch "Editor enhancements enabled" off. The gallery
639
- button, controls and initial-state logic disappear and the native picker and
1018
+ - **From the settings page**: switch "Editor enhancements enabled" off. The
1019
+ gallery, the zone bars and the initial-state logic disappear and the native picker and
640
1020
  accordions remain. Content, config, media and unsaved edits are preserved; the
641
1021
  form is not reloaded. The settings page stays available to turn it back on.
642
- - **Emergency, outside the panel**: start the server with
1022
+ - **Emergency, outside the admin**: start the server with
643
1023
  `BLOCKSCENE_DISABLED=true` (or set `config.disabled: true`). It is read at
644
1024
  boot and reported by the catalog, wins over the saved settings and cannot be
645
1025
  undone from the client. Changing it requires a restart.
646
- - **If the catalog request fails**, the panel renders nothing and the native
1026
+ - **If the catalog request fails**, the plugin renders nothing and the native
647
1027
  editor is used, so a plugin outage does not block editing.
648
1028
  - **Completely**: set `'blockscene': { enabled: false }` in `config/plugins.js`
649
1029
  and rebuild/restart. This is the only way out of an import/build failure; a
@@ -701,10 +1081,10 @@ installs without overrides, not a promise for every minor in between. Strapi
701
1081
  - Authenticated admin catalog endpoint exposes only supported metadata, resolved
702
1082
  override URLs, palette and editor preferences; never the whole config.
703
1083
  - Top-level Dynamic Zones only. Conditional fields are omitted conservatively.
704
- - "Open all" / "Close all" drive the native accordions without touching the
705
- whole list at once: open all opens the rows in and near the visible area, a
706
- few per frame, and the others as they approach while scrolling (until Close
707
- all or leaving the document); close all closes the visible rows at once and
1084
+ - "Expand all" / "Collapse all" drive the native accordions without touching the
1085
+ whole list at once: expanding opens the rows in and near the visible area, a
1086
+ few per frame, and the others as they approach while scrolling (until Collapse
1087
+ all or leaving the document); collapsing closes the visible rows at once and
708
1088
  the rest in idle time. With lazy editors, on a 34-block page with CKEditor
709
1089
  fields (Strapi 5.31) the longest pause went from about 2.9 s to 0.14 s on
710
1090
  open all and from 1.6 s to 0.14 s on close all.