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

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.
Files changed (5) hide show
  1. package/README.md +432 -39
  2. package/dist/admin.cjs +4207 -1330
  3. package/dist/admin.mjs +4218 -1317
  4. package/dist/server.cjs +1102 -20
  5. package/package.json +1 -1
package/README.md CHANGED
@@ -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** (Strapi 5, 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
 
@@ -152,14 +186,14 @@ Each block header gets a few icons right before Strapi's own delete button
152
186
  nested components, new row key, media kept, relations kept (Strapi 5 reads
153
187
  the source row's relations from the server and adds its unsaved changes).
154
188
  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
189
+ - **Copy / Paste** across pages. The copy icon copies one block; "Select
190
+ blocks" in the zone bar's "…" menu shows a checkbox per row, then Copy. The
157
191
  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
192
+ with the source content type and locale). "Paste N blocks" appears in the
193
+ zone bar's "…" menu of every zone that allows all the copied components, and
194
+ in the page preview seams. Paste is all or nothing: every block must be
195
+ allowed in the target zone, fit its maximum, and groups must be whole;
196
+ otherwise a notice explains why and the form is not changed. Media and relations are kept by id/documentId, so paste
163
197
  within the same install. Option `clipboard`.
164
198
  - **Confirm delete**. The native delete asks "Delete block …?" first;
165
199
  confirming runs Strapi's own removal. On a group marker the dialog says only
@@ -172,10 +206,14 @@ block).
172
206
 
173
207
  **The hidden attribute is a database column.** At register time the plugin adds
174
208
  a boolean attribute (like i18n adds `locale`) to every component used in a
175
- Dynamic Zone: `{ type: 'boolean', default: false, visible: false,
176
- configurable: false }`, so Strapi's schema sync creates a `bs_hidden` column in
177
- each of those component tables on the next start (existing rows read as not
178
- hidden). It is hidden from the edit view. Rename it with the plugin config
209
+ Dynamic Zone: `{ type: 'boolean', default: false, configurable: false }`, so
210
+ Strapi's schema sync creates a `bs_hidden` column in each of those component
211
+ tables on the next start (existing rows read as not hidden). It is never shown
212
+ as an input: the plugin removes it from the components' edit layouts through
213
+ the Content Manager's layout hook (it may be listed in "Configure the view" of
214
+ a component; leave it out of the layout). It is not marked `visible: false`,
215
+ because before Strapi 5.45 the edit view crashes on a value whose attribute the
216
+ admin does not know. Rename it with the plugin config
179
217
  `hiddenAttribute: 'myName'`, or set `hiddenAttribute: false` to add nothing (no
180
218
  eye, nothing stripped). A component that already has an attribute of that name
181
219
  with another type is skipped with a warning. Removing the plugin (or setting
@@ -218,10 +256,12 @@ needed for the gallery to pick them up. Reading requires the
218
256
  typology ("Automatic" shows the guess) and tags (comma separated).
219
257
  - Editor preferences: enhancements on/off, visibility of each collective
220
258
  button, initial accordion state and the initial block mode.
259
+ - Version history (Strapi 5): module switch, covered content types, retention
260
+ (see "Version history").
221
261
  - Content types: every project type with a Dynamic Zone, each with on/off
222
262
  (off leaves its edit view fully native) and, on Strapi 5, the mode its edit
223
- views open in ("Default" follows the global preview mode) and its visual
224
- editor sidebar (see below).
263
+ views open in ("Default" follows the global preview mode), its visual
264
+ editor sidebar and optionally its own page preview toolbar (see below).
225
265
  - "Restore project defaults" (or "Reset to defaults" when the project has no
226
266
  code defaults), after a confirmation, deletes what was saved here
227
267
  (`DELETE /blockscene/settings`, same permission as saving).
@@ -253,7 +293,12 @@ module.exports = {
253
293
  The effective settings are the built-in defaults, then the code `settings`,
254
294
  then what was saved on the page:
255
295
 
256
- - `palette` and `editor`: a saved key wins over the code key by key.
296
+ - `palette` and `editor`: a saved key wins over the code key by key, except
297
+ an empty `previewUrl` or `blockPreviewUrl`, which means "not set": the code
298
+ value applies. Clearing either field on the page therefore falls back to the
299
+ project default (there is no saved "off" for a URL the code provides; remove
300
+ it from the code settings, or set `previewMode` per content type, to keep a
301
+ type out of the page preview).
257
302
  - `components` and `contentTypes`: a saved entry replaces the code entry for
258
303
  that uid as a whole (a saved `api::page.page` entry replaces its code
259
304
  sidebar too); other uids keep their code values.
@@ -271,8 +316,8 @@ ignored (boot continues). `GET /blockscene/settings` returns them as
271
316
  | Option | Values | Default |
272
317
  | --- | --- | --- |
273
318
  | Editor enhancements enabled | on/off | on |
274
- | Show "Open all blocks" | yes/no | yes |
275
- | Show "Close all blocks" | yes/no | yes |
319
+ | Show "Expand all" (`showOpenAll`) | yes/no | yes |
320
+ | Show "Collapse all" (`showCloseAll`) | yes/no | yes |
276
321
  | Show block thumbnails in the rows (`showRowThumbnails`) | yes/no | yes |
277
322
  | Initial accordion state | all closed / all open / remember | all closed |
278
323
  | Load rich-text editors on demand (`lazyEditors`, Strapi 5) | yes/no | yes |
@@ -284,13 +329,18 @@ ignored (boot continues). `GET /blockscene/settings` returns them as
284
329
  | Duplicate on each block (`duplicate`) | yes/no | yes |
285
330
  | Copy and Paste (`clipboard`) | yes/no | yes |
286
331
  | Blocks hidden on the site (`hiddenBlocks`) | `strip` / `flag` / `off` | `strip` |
332
+ | Friendly field labels (`friendlyLabels`, see [Field labels](#field-labels)) | yes/no | yes |
333
+ | Page preview toolbar (`previewToolbar`, Strapi 5, see [Pane toolbar](#pane-toolbar)) | ordered list of `modes`, `history`, `devices`, `status`, `actions` | all five |
334
+ | Page widths (`previewDevices`, Strapi 5) | 1 to 8 of `fit`, `mobile`, `tablet`, `desktop`, `{ label, width }` | the four built-in |
287
335
 
288
336
  Every edit view opens in its content type's mode, else the initial preview
289
337
  mode. Editors can switch while they edit; the switch is not remembered.
290
338
 
291
339
  The initial state is applied once per document/locale after the blocks
292
- render; edits, reorders and newly inserted blocks are not re-applied. Hiding a
293
- button only hides it; native accordion headers keep working.
340
+ render; edits, reorders and newly inserted blocks are not re-applied.
341
+ `showOpenAll` and `showCloseAll` shape the zone bar's single toggle (both: it
342
+ toggles; one: only that action; none: no button). Hiding it only hides it;
343
+ native accordion headers keep working.
294
344
 
295
345
  Lazy editors: a custom field listed in `lazyFields` first shows a read-only
296
346
  preview of its HTML (same label, hint and error; scripts, frames, styles, event
@@ -302,7 +352,7 @@ registered custom field at boot and passes the others straight through; until
302
352
  the catalog loads, and when the plugin or the content type is off, every field
303
353
  is native.
304
354
 
305
- "Remember" stores only the last explicit open all / close all click, as
355
+ "Remember" stores only the last explicit Expand all / Collapse all click, as
306
356
  `open`/`closed` in the browser's localStorage, keyed by admin URL, user id,
307
357
  content type and zone. Another user in the same browser does not inherit it.
308
358
  Blocked storage or an invalid value falls back to all closed. No sync between
@@ -314,9 +364,10 @@ the two supported versions only.
314
364
 
315
365
  ## Whole-page preview (Strapi 5)
316
366
 
317
- The Strapi 5 distribution adds a "Page preview" panel with three modes: form,
318
- side by side (resizable pane, native actions moved to a bar above the form) and
319
- preview. The panel posts the live values of the first Dynamic Zone to a page
367
+ The Strapi 5 distribution adds a page preview with three modes, chosen from the
368
+ first zone's bar and from the pane toolbar: Fields (form), Fields + page (side
369
+ by side: resizable pane, native actions moved to a bar above the form) and
370
+ Visual editor. It posts the live values of the first Dynamic Zone to a page
320
371
  your frontend serves; the page renders them with its own components and styles
321
372
  and can ask the admin to open a block, edit a field or pick media. Nothing is
322
373
  saved or published by the preview.
@@ -386,7 +437,7 @@ Minimal configuration:
386
437
  Use the same host name the frontend dev server was started for
387
438
  (`localhost` vs `127.0.0.1`): Next 16 refuses its dev WebSocket for the
388
439
  other one and the page then never hydrates, so `ready` never arrives and
389
- the panel reports the preview as unavailable with no error in either console.
440
+ the pane reports the preview as unavailable with no error in either console.
390
441
 
391
442
  The bridge only exposes what the schema allows: attributes marked `private`
392
443
  are never projected, focused or edited; media URLs are passed through for any
@@ -404,15 +455,91 @@ page updates while you type; Done, Esc or the backdrop put the block back. Side
404
455
  by side focuses the field in the form on the left instead. Media
405
456
  fields open the native Media Library. Everything is validated against the schema
406
457
  and the zone's edit permission; a published page never receives unsaved values
407
- by itself. Strapi 4 does not have this panel yet (see `docs/BACKLOG.md`).
458
+ by itself. Strapi 4 does not have the page preview yet (see `docs/BACKLOG.md`).
459
+
460
+ ### Pane toolbar
461
+
462
+ Which controls the toolbar shows, and in which order, is `previewToolbar`:
463
+ `modes` (Fields, Fields + page, Visual editor), `history` (Undo, Redo),
464
+ `devices` (the width menu), `status` (Draft/Modified/Published and the unsaved
465
+ hint) and `actions` (Save, Publish). A missing id is hidden; `status` and
466
+ `actions` stay one group on the right, placed where the first of them is
467
+ listed. The preview's loading or error notice always shows. Without `modes` the
468
+ pane stays in the mode the edit view opened in (for example a visual editor
469
+ only view with `previewMode: 'preview'`); the form's zone bar keeps its own
470
+ Undo, Redo and mode menu.
471
+
472
+ `previewDevices` lists the width menu's entries in order; the first is used
473
+ until an editor picks another. Besides the four names, `{ label, width }` adds
474
+ a custom width (label up to 24 characters, width a whole number from 240 to
475
+ 3840 px; no name or width twice; up to 8 entries). With a single entry the menu
476
+ is hidden.
477
+
478
+ ```js
479
+ // Settings, Blockscene (Editor preferences, or per content type), PUT /blockscene/settings, or the code `settings`:
480
+ editor: {
481
+ previewToolbar: ['modes', 'devices', 'actions'],
482
+ previewDevices: ['fit', 'mobile', { label: 'Laptop', width: 1280 }, 'desktop'],
483
+ },
484
+ contentTypes: {
485
+ 'api::landing.landing': { previewToolbar: ['actions'], previewDevices: ['desktop'] },
486
+ }
487
+ ```
488
+
489
+ A content type's value replaces the global one. The Settings page edits both as
490
+ checklists (check to show, arrows to order, "Add a width" for custom widths);
491
+ "Own page preview toolbar" under a content type starts as a copy of the global
492
+ choice, and turning it off goes back to the global one.
493
+
494
+ ### Custom sidebar panels
495
+
496
+ A project (in its `src/admin/app.tsx`) or another plugin can add its own panel
497
+ to the visual editor sidebar. Registered panels come after the configured field
498
+ items, in registration order; the rail shows in the Visual editor mode as soon
499
+ as there is one item.
500
+
501
+ ```tsx
502
+ // src/admin/app.tsx
503
+ const Notes = ({ values, onChange, disabled, close }) => (
504
+ <textarea value={values.notes ?? ''} disabled={disabled}
505
+ onChange={(e) => onChange('notes', e.target.value)} />
506
+ )
507
+ export default {
508
+ bootstrap(app) {
509
+ app.getPlugin('blockscene')?.apis.registerPanel({
510
+ id: 'notes', // 1 to 40 letters, digits, _ or -; the same id again replaces the panel
511
+ label: 'Notes', // 1 to 40 characters
512
+ icon: 'info', // optional: an icon name (the sidebar icons above, for example) or a React element
513
+ open: 'drawer', // 'drawer' (default) or 'modal'
514
+ contentTypes: ['api::page.page'], // optional: only these types (default: every type)
515
+ Component: Notes,
516
+ })
517
+ },
518
+ }
519
+ ```
520
+
521
+ `Component` receives `model` (content type uid), `documentId` (undefined while
522
+ creating), `locale`, `values` (the live form values), `onChange(name, value)`
523
+ (the form's own setter: the change is unsaved, goes through undo and redo, and
524
+ Save or Publish store it with the usual permission checks), `disabled` (the form
525
+ is read-only) and `close()`. It renders inside the Content Manager edit view, so
526
+ it can also use the admin's hooks (`useForm`, `useFetchClient`...). Strapi runs
527
+ every plugin's `register` before any `bootstrap`, then the host's `bootstrap`:
528
+ calling `registerPanel` from either bootstrap works. An invalid panel is logged
529
+ (`[blockscene] registerPanel: ...`) and ignored; `registerPanel` returns `true`
530
+ or `false`. A panel that throws while rendering shows the plugin's error notice
531
+ in its drawer; the editor keeps working. Strapi 5 only (the Strapi 4 package has
532
+ no visual editor).
408
533
 
409
534
  ### Undo and redo
410
535
 
411
- The Blockscene panel and the pane toolbar have Undo and Redo buttons (Strapi 5).
536
+ The first zone's bar and the pane toolbar have Undo and Redo buttons (Strapi 5),
537
+ on one shared history (the shortcuts use it too).
412
538
  They step through the whole edit view: blocks inserted, removed or moved from the
413
539
  gallery, the page, groups or the native actions, and any field edit. Changes
414
540
  within 400 ms of each other are one step, so a burst of typing undoes at once;
415
- up to 100 steps. Shortcuts: Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z and Ctrl+Y, only while
541
+ a block dragged with the mouse is one step however long the drag lasts; up to
542
+ 100 steps. Shortcuts: Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z and Ctrl+Y, only while
416
543
  the focus is outside a text field, select or rich text editor (those keep their
417
544
  own undo).
418
545
 
@@ -460,7 +587,7 @@ With a valid map:
460
587
  groups may nest; a CLOSE right after its OPEN is a valid empty group. Errors
461
588
  are `closeBeforeOpen` (CLOSE with no open group), `mismatch` (CLOSE is not the
462
589
  one expected by the innermost open group) and `unclosed` (OPEN never closed).
463
- - Nothing is repaired on read: the panel lists the problems (row, marker) and
590
+ - Nothing is repaired on read: the Blockscene side panel lists the problems (row, marker) and
464
591
  the stray rows stay visible so the editor can fix them. Drafts save normally.
465
592
  - Publishing is refused server-side while a configured pair is unbalanced.
466
593
  Strapi 5: a document-service middleware intercepts every facade path that
@@ -484,6 +611,272 @@ With a valid map:
484
611
  Configured groups are stored exactly as before: the flat list of components in
485
612
  the zone. Removing the config only removes the group tools and the guard.
486
613
 
614
+ ### Groups in the form
615
+
616
+ The edit view draws the same structure over Strapi's native rows (Strapi 5 and
617
+ 4; data attributes on the rows, nothing of the Content Manager is patched):
618
+
619
+ - The rows between an OPEN and its CLOSE are indented under it with a guide line
620
+ in the admin's primary colour, one level (and one line) per nested group.
621
+ - The CLOSE row reads as the group's end: compact, dimmed, without a drag handle
622
+ (it cannot be dragged on its own; its delete stays, with the marker notice).
623
+ - Each closed group's OPEN header gets a chevron before its title. It folds the
624
+ group: its rows and CLOSE are hidden and the header shows "N blocks" (every
625
+ row inside, nested ones too, CLOSE markers excluded). Folding is kept in memory
626
+ for the session, per document, zone and group; a reload shows every group open.
627
+ A validation error inside a folded group (on Save or Publish) unfolds it.
628
+ Expand all / Collapse all act on the visible block accordions only: they never
629
+ open the forms of a folded group's rows, and a group keeps its own chevron.
630
+ - Moving an OPEN moves its group. Nothing intercepts Strapi's drag and drop:
631
+ after any change of the zone, if the only thing that changed is one OPEN
632
+ moving, its members as they were (children and CLOSE) are put back right after
633
+ it, in the same unsaved form change. This covers the mouse (settled on drop:
634
+ Strapi moves the row on every hover), the keyboard (Space, arrows, Space on the
635
+ drag handle) and the move arrows of small screens. An OPEN dropped among its
636
+ own rows (a single keyboard or arrow step down) moves the group one row down
637
+ instead. The whole drag and the correction are one undo step (Strapi 5).
638
+ - Everything else keeps the flat-list meaning: a child dragged out of the range
639
+ is an ordinary block from then on, a block dropped inside becomes a child, and
640
+ an OPEN dropped inside another group is nested there with its rows. A CLOSE
641
+ moved by other means (the small-screen arrows) ends the group where it lands.
642
+ Unclosed groups and stray markers are indented as the page preview shows them
643
+ but have no chevron and are never moved (the diagnostics list them).
644
+
645
+ ## Field labels
646
+
647
+ The Content Manager shows each field under its "Configure the view" label, which
648
+ defaults to the raw attribute name (`mobileColumnsCount`). Blockscene rewrites
649
+ the edit view labels, hints and placeholders through the Content Manager's own
650
+ `Admin/CM/pages/EditView/mutate-edit-view-layout` hook (Strapi 5 and 4, the
651
+ same hook the i18n plugin uses), so nothing in the DOM is patched and the
652
+ saved view configuration is never changed. Per field, the first that applies:
653
+
654
+ 1. **Project texts** from the plugin config `fields` (below);
655
+ 2. **a label set in "Configure the view"** (anything other than the attribute name);
656
+ 3. **the humanized name** when "Friendly field labels" is on: `mobileColumnsCount`
657
+ reads "Mobile columns count", `page_seo` "Page seo", acronym runs stay
658
+ (`pageSEO` "Page SEO", `ctaURL` "Cta URL").
659
+
660
+ Description (the hint under the field) and placeholder change only when the
661
+ project texts have them. Texts follow the admin user's interface language
662
+ (`strapi-admin-language`, else English), resolved as: exact locale, then its
663
+ language (`pt-BR` → `pt`), then any variant of that language, then `en`, then
664
+ the first value.
665
+
666
+ ```js
667
+ // config/plugins.js
668
+ blockscene: { config: { fields: {
669
+ 'api::page.page': {
670
+ pageSeo: { label: { en: 'Search engine settings', 'pt-BR': 'Configurações de SEO' },
671
+ description: { en: 'Title and description shown on Google', 'pt-BR': 'Título e descrição no Google' } },
672
+ },
673
+ 'shared.seo': { metaTitle: { placeholder: 'Up to 60 characters' },
674
+ metaDescription: { help: { en: 'Shown under the title in search results. Google cuts it around 155 characters; write for people, not keywords.' } } },
675
+ } } },
676
+ ```
677
+
678
+ Keys are content type or component uids, then attribute names; each of
679
+ `label` (up to 80 characters), `description` (300), `placeholder` (120) and
680
+ `help` (500) is a string or `{ "<locale>": string }`.
681
+
682
+ `description` stays the line under the input. `help` is for the longer
683
+ explanation that would stretch the form: on Strapi 5 it becomes an "i" icon next
684
+ to the label, in the Content Manager's own label action slot (where the i18n
685
+ plugin puts its globe; both icons show on localized fields), with the text in a
686
+ tooltip on hover and on keyboard focus (the button is reachable with Tab and
687
+ named "More information"). A field can have both. On Strapi 4 the Content
688
+ Manager gives component fields, where blocks live, no label action, so `help`
689
+ is appended to the description under the input instead. The map is validated at boot: an unknown
690
+ key, an empty or too long text, or a malformed locale code logs a warning
691
+ naming it and the whole `fields` config is ignored (labels fall back to the two
692
+ other steps). Entries for a uid or attribute the schema no longer has (a
693
+ renamed or removed field) are skipped with one warning listing them; the rest
694
+ keep working. It is code only, versioned with the
695
+ project; the Settings page has the on/off switch but does not edit texts.
696
+
697
+ To start the file, generate every field of a project with its humanized English
698
+ label from a clone of this repository, then keep what you want to change:
699
+
700
+ ```bash
701
+ node scripts/fields-skeleton.mjs ../my-project/backend > ../my-project/backend/config/blockscene-fields.json
702
+ # config/plugins.js: fields: require('./blockscene-fields.json')
703
+ ```
704
+
705
+ Limits: the hook runs synchronously and reads the last catalog the admin
706
+ loaded. The first edit or list view of a session starts that request and shows
707
+ native labels; the next layout computed (another document, list → edit, a
708
+ locale switch) has them. Strapi 5 gives the hook no content type uid, so the
709
+ layout is matched by display name and field names; two content types that
710
+ share both are left untouched (component fields always work: they are keyed by
711
+ uid). The switch "Editor enhancements enabled", `BLOCKSCENE_DISABLED` and a
712
+ content type turned off all leave the native labels; any error leaves the
713
+ layout untouched. Changing the interface language applies from the next edit
714
+ view.
715
+
716
+ ## Version history (Strapi 5)
717
+
718
+ Off by default. Turn it on in Settings, Blockscene, "Version history" (or from
719
+ code, `settings.history`, see below). Strapi Community has no content history
720
+ (it is a Growth feature); this module records one event per content change and
721
+ keeps a snapshot of the content, so an editor can see what changed and load an
722
+ earlier version back into the form.
723
+
724
+ **What it captures.** Every create, save, publish, unpublish, discard draft and
725
+ delete of the covered content types (all `api::` types by default, or a list)
726
+ made through Strapi's document service: the admin (single and bulk actions),
727
+ the REST and GraphQL APIs, and any plugin or custom code that uses
728
+ `strapi.documents`. One event per affected locale, with who (admin user, API
729
+ token, Users & Permissions user, or `system`), when, the action and a summary
730
+ (blocks added, removed and changed per Dynamic Zone, top-level fields changed)
731
+ against the previous snapshot of the same locale. The snapshot is the stored
732
+ entry with components and Dynamic Zones in full, relations as documentIds and
733
+ media as file ids (a 34-block page is a few KB; the capture adds a few
734
+ milliseconds to a save). A snapshot identical to the previous one is not stored
735
+ again (the event is, with the same hash).
736
+
737
+ **What it does not see.** Writes that bypass the document service: raw
738
+ `strapi.db.query`, knex or SQL, imports that write the database directly. This
739
+ is content activity for editors, not a security audit log.
740
+
741
+ **Failure policy.** A failed capture never blocks a save or a publish: the
742
+ change goes through, the error is logged and the event is marked "Not
743
+ recorded". A failed capture fails a delete: the delete runs in one transaction
744
+ with its events, so nothing is deleted without its last version (a bulk delete
745
+ is rolled back whole). `BLOCKSCENE_DISABLED=true` or the module switch off stop
746
+ all capture. The plugin never records its own content types.
747
+
748
+ **In the edit view.** The Blockscene side panel gets a History section (saved
749
+ documents of covered types, also on types without a Dynamic Zone): the
750
+ versions of the document in the current locale, newest first. Selecting one
751
+ shows a block-level diff against the form (relations are not compared) and
752
+ "Load this version", which puts the version into the form the way undo does:
753
+ nothing is written until the editor saves, so validation, permissions and
754
+ lifecycles apply as for any edit, and undo reverts the load. Components and
755
+ blocks come back as new rows; relations become the connect/disconnect change
756
+ from what the document has now; fields the editor may not update, fields the
757
+ schema no longer has, blocks whose component is no longer allowed, and media or
758
+ related entries that no longer exist are left out and listed.
759
+
760
+ **Permission.** `Blockscene: Read version history` (`history.read`), plus read
761
+ access to the content type in the Content Manager. Snapshots hold removed and
762
+ unpublished text, so the section is hidden without it. Admin API:
763
+ `GET /blockscene/history/:uid/:documentId?locale=` (the latest 100 events, no
764
+ snapshots) and `GET /blockscene/history-events/:id` (one event, its snapshot and
765
+ the media and relation targets that still exist).
766
+
767
+ **Retention.** Snapshots are kept `retentionDays` (90) and at most
768
+ `maxSnapshots` (100) per document, newest first; events without a snapshot are
769
+ kept `eventDays` (365, at least `retentionDays`). A cron job added by the plugin
770
+ runs nightly at 03:00 server time, in batches of 500; it only empties snapshots
771
+ and deletes events, never media files, and running it on several instances is
772
+ harmless.
773
+
774
+ ```js
775
+ // config/plugins.js
776
+ blockscene: { config: { settings: { history: { enabled: true, contentTypes: ['api::page.page', 'api::post.post'],
777
+ retentionDays: 90, maxSnapshots: 100, eventDays: 365, trashDays: 90 } } } }
778
+ ```
779
+
780
+ Events live in the `blockscene_events` table (content type
781
+ `plugin::blockscene.event`, hidden from the Content Manager and the Content-Type
782
+ Builder). Removing the plugin, or installing a Blockscene version older than
783
+ the one that added history, lets Strapi drop that table and every recorded
784
+ version on the next start: back up `blockscene_events` first if you need it.
785
+
786
+ ### Trash (Strapi 5)
787
+
788
+ Part of the version history module: on with it, for the same content types.
789
+ Strapi has no trash in any plan; here a delete made through the document service
790
+ (the admin's Delete, bulk delete and "Delete locale", the APIs, plugins) also
791
+ writes one trash entry per document, in the same transaction as the delete.
792
+
793
+ **What an entry keeps.** Every deleted locale, draft and published rows (they
794
+ can differ), as snapshots like the history's; the document's title (its Content
795
+ Manager main field); who deleted it and when; and the links other documents and
796
+ blocks had to it. Strapi removes those links with the document, and the
797
+ document's own snapshot does not hold them when the relation is unidirectional
798
+ (declared only on the other side, like a menu listing pages or a block linking
799
+ to a page), so they are captured before the delete: owner, field, locale and the
800
+ position in the owner's list. A delete without a locale removes the default
801
+ locale only (Strapi's rule), `'*'` every locale; the entry has exactly what was
802
+ deleted. If the capture fails, nothing is deleted.
803
+
804
+ **Restore.** From the Trash page, which first shows what the restore would do
805
+ and writes nothing if something blocks it:
806
+
807
+ - Blocking: the content type is gone; a unique field (`unique: true` or a uid
808
+ field, such as a slug) is now used by another document in that locale; the
809
+ document has that locale again; a single type has a document again; none of
810
+ its locales is configured any more.
811
+ - Reported, then restored anyway: a published version existed (see below);
812
+ required fields are empty under the current schema (drafts may be, Strapi
813
+ checks them on publish); fields the schema no longer has, blocks whose
814
+ component the zone no longer allows, media files and related documents that no
815
+ longer exist (left out); locales no longer configured (skipped); links whose
816
+ owner is gone or now points elsewhere.
817
+
818
+ The document comes back as a **draft with its draft content**: the draft is the
819
+ latest work, and publishing is the editor's decision (the site may have moved
820
+ on). The published snapshot stays in the entry and in the preview. Every locale
821
+ is restored under one documentId, the default locale first, through the
822
+ document service (validation and the project's lifecycles run). A document that
823
+ is gone gets a new documentId, since Strapi's create does not take one; a locale
824
+ deleted from a document that still exists goes back into that document. The
825
+ captured links are put back where they were (same position in the owner's
826
+ list) when the owner still exists; links from published versions come back
827
+ when those owners are published again after this document is. A restore is one
828
+ transaction and is idempotent: the entry is claimed first, so a second restore
829
+ (a double click, another editor, another instance) returns the first one's
830
+ document. The history gets a `restore` event per locale.
831
+
832
+ **Delete forever** removes one entry (recorded as a `purge` event in the
833
+ activity). The nightly job deletes entries past their expiry, restored ones too,
834
+ in batches; neither ever deletes media files.
835
+
836
+ **Retention.** `trashDays` (90, 1 to 3650) in the same settings; each entry's
837
+ expiry is fixed when the document is deleted, so a later change applies to later
838
+ deletes.
839
+
840
+ ```js
841
+ blockscene: { config: { settings: { history: { enabled: true, trashDays: 30 } } } }
842
+ ```
843
+
844
+ Entries live in `blockscene_trash` (`plugin::blockscene.trash`, hidden from the
845
+ Content Manager and the Content-Type Builder); the same backup note as for
846
+ `blockscene_events` applies.
847
+
848
+ ### Activity and Trash pages (Strapi 5)
849
+
850
+ A main menu link, "Activity and trash", opens one page with a tab each (the
851
+ pages are tools for editors, not configuration, so they are not under
852
+ Settings). **Activity** lists every recorded event, newest first, 25 per page,
853
+ filtered by person, content type, action, date range and title (documents whose
854
+ main field contains the text now, or whose trash entry's title does); each row
855
+ links to the document's edit view while that locale exists and shows the
856
+ summary. **Trash** lists the entries (title, type, locales, who, when, when it
857
+ is deleted for good), filtered by content type, with restored entries on a
858
+ switch; Preview shows the fields and blocks of each deleted locale, read-only;
859
+ Restore shows the pre-check report and asks to confirm; Delete forever asks
860
+ first.
861
+
862
+ **Permissions.** `Read content activity` (`activity.read`), `Read the trash`
863
+ (`trash.read`), `Restore from the trash` (`trash.restore`), `Delete from the
864
+ trash forever` (`trash.purge`), none granted to non-super-admin roles by
865
+ default. On top of them, every list shows only content types the user may read
866
+ in the Content Manager (an entry of another type answers 404), restoring needs
867
+ create there and deleting forever needs delete. Admin API: `GET /blockscene/activity`
868
+ (`actor`, `contentType`, `action`, `from`, `to` as ISO dates, `q`, `page`,
869
+ `pageSize` up to 100), `GET /blockscene/trash` (`contentType`, `status`
870
+ trashed or restored, `page`, `pageSize`), `GET /blockscene/trash/:id` (the
871
+ preview), `GET /blockscene/trash/:id/check` (the pre-check),
872
+ `POST /blockscene/trash/:id/restore` (409 with the report when blocked) and
873
+ `DELETE /blockscene/trash/:id`. Lists never load snapshots.
874
+
875
+ **Coverage boundary**, the same as the history's: the document service only.
876
+ A raw `strapi.db.query` delete, SQL or a database restore leaves no trash
877
+ entry; deleting a content type (Content-Type Builder) drops its data without
878
+ one. The trash is a safety net for editors, not a backup: keep database backups.
879
+
487
880
  ## Languages
488
881
 
489
882
  All plugin chrome (gallery, settings, page preview, editor dialogs, diagnostics)
@@ -497,15 +890,15 @@ component display names are never translated by the plugin.
497
890
 
498
891
  ## Turning the editor enhancements off
499
892
 
500
- - **From the panel**: switch "Editor enhancements enabled" off. The gallery
501
- button, controls and initial-state logic disappear and the native picker and
893
+ - **From the settings page**: switch "Editor enhancements enabled" off. The
894
+ gallery, the zone bars and the initial-state logic disappear and the native picker and
502
895
  accordions remain. Content, config, media and unsaved edits are preserved; the
503
896
  form is not reloaded. The settings page stays available to turn it back on.
504
- - **Emergency, outside the panel**: start the server with
897
+ - **Emergency, outside the admin**: start the server with
505
898
  `BLOCKSCENE_DISABLED=true` (or set `config.disabled: true`). It is read at
506
899
  boot and reported by the catalog, wins over the saved settings and cannot be
507
900
  undone from the client. Changing it requires a restart.
508
- - **If the catalog request fails**, the panel renders nothing and the native
901
+ - **If the catalog request fails**, the plugin renders nothing and the native
509
902
  editor is used, so a plugin outage does not block editing.
510
903
  - **Completely**: set `'blockscene': { enabled: false }` in `config/plugins.js`
511
904
  and rebuild/restart. This is the only way out of an import/build failure; a
@@ -563,10 +956,10 @@ installs without overrides, not a promise for every minor in between. Strapi
563
956
  - Authenticated admin catalog endpoint exposes only supported metadata, resolved
564
957
  override URLs, palette and editor preferences; never the whole config.
565
958
  - Top-level Dynamic Zones only. Conditional fields are omitted conservatively.
566
- - "Open all" / "Close all" drive the native accordions without touching the
567
- whole list at once: open all opens the rows in and near the visible area, a
568
- few per frame, and the others as they approach while scrolling (until Close
569
- all or leaving the document); close all closes the visible rows at once and
959
+ - "Expand all" / "Collapse all" drive the native accordions without touching the
960
+ whole list at once: expanding opens the rows in and near the visible area, a
961
+ few per frame, and the others as they approach while scrolling (until Collapse
962
+ all or leaving the document); collapsing closes the visible rows at once and
570
963
  the rest in idle time. With lazy editors, on a 34-block page with CKEditor
571
964
  fields (Strapi 5.31) the longest pause went from about 2.9 s to 0.14 s on
572
965
  open all and from 1.6 s to 0.14 s on close all.
@@ -584,7 +977,7 @@ uploads share an origin.
584
977
  ## Development
585
978
 
586
979
  - `admin/`: shared gallery, settings page, wireframes and version-specific adapters.
587
- - `server/`: settings store, validation, permissions, the catalog endpoint and the layout-group publish guard (`groups.js` is shared with the admin).
980
+ - `server/`: settings store, validation, permissions, the catalog endpoint and the layout-group publish guard (`groups.js` is shared with the admin); `history.js` and `diff.js` for version history (`diff.js` is shared with the admin).
588
981
  - `admin/translations/`: one flat catalogue per admin locale (English is the source of truth).
589
982
  - `scripts/capture-previews.mjs`: local thumbnail capture.
590
983
  - `packages/strapi4`, `packages/strapi5`: independently installable distributions.