@aduptive/strapi-blockscene 1.0.0-alpha.1 → 1.0.0-alpha.11
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 +1017 -54
- package/dist/admin.cjs +8675 -550
- package/dist/admin.mjs +8699 -523
- package/dist/server.cjs +1865 -48
- package/package.json +3 -4
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
|
|
5
|
-
|
|
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
|
|
@@ -29,13 +29,18 @@ module.exports = {
|
|
|
29
29
|
config: {
|
|
30
30
|
previewBaseUrl: '/block-previews',
|
|
31
31
|
previewVersion: '2026-09-22', // optional cache buster appended as ?v=
|
|
32
|
+
// Optional: only these Strapi component folders get cards in Settings > Block gallery.
|
|
33
|
+
// Dynamic Zone insertion still follows each content type's own schema.
|
|
34
|
+
settingsComponentCategories: ['blocks'],
|
|
32
35
|
components: {
|
|
33
36
|
'blocks.hero': {
|
|
34
37
|
label: 'Hero banner',
|
|
35
38
|
description: 'Introduce a page with a prominent title and image.',
|
|
36
|
-
|
|
39
|
+
typology: 'hero', // optional; guessed from the name otherwise
|
|
40
|
+
tags: ['Editorial'],
|
|
37
41
|
keywords: 'heading introduction',
|
|
38
42
|
image: '/block-previews/hero.png',
|
|
43
|
+
showRowThumbnail: false, // optional per-component row thumbnail
|
|
39
44
|
},
|
|
40
45
|
},
|
|
41
46
|
},
|
|
@@ -44,19 +49,114 @@ module.exports = {
|
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
Rebuild/restart the admin. Open a content type with a Dynamic Zone. The
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
native "Add a component to <zone>" button of each editable, non-full zone opens
|
|
53
|
+
the gallery instead of Strapi's category picker; a full zone keeps its native
|
|
54
|
+
button.
|
|
55
|
+
|
|
56
|
+
### Zone bar
|
|
57
|
+
|
|
58
|
+
Right beside the native zone label ("blocks (3)") each zone gets a short bar:
|
|
59
|
+
|
|
60
|
+
- **Expand all / Collapse all**: one button with text. It reads the rows' own
|
|
61
|
+
state: "Expand all" while any block of the zone is collapsed, "Collapse all"
|
|
62
|
+
when every block is open (a header opened or closed by hand counts too), and
|
|
63
|
+
exposes it as `aria-expanded`. Blocks open or close a few at a time around the
|
|
64
|
+
viewport first (long pages stay responsive). Rows of a folded layout group
|
|
65
|
+
are left alone (see [Groups in the form](#groups-in-the-form)). Options `showOpenAll` /
|
|
66
|
+
`showCloseAll`: with both on (the default) the button toggles; with only one
|
|
67
|
+
on it offers that action alone, disabled while there is nothing to do; with
|
|
68
|
+
both off there is no button.
|
|
69
|
+
- **Undo and Redo** (Strapi 5, first zone only): icon and text, the shortcut in
|
|
70
|
+
the tooltip. See [Undo and redo](#undo-and-redo).
|
|
71
|
+
- **Editing mode** (first zone only, when a preview route exists): a
|
|
72
|
+
menu with Fields only, Fields + preview and Visual editor. Without a preview route the
|
|
73
|
+
menu is not shown; Settings, Blockscene says why.
|
|
74
|
+
- **"…" (More actions)**: Select blocks, and "Paste N blocks" while the
|
|
75
|
+
clipboard holds blocks this zone allows (see [Row actions](#row-actions)).
|
|
76
|
+
No entries, no menu.
|
|
77
|
+
|
|
78
|
+
"Select blocks" swaps the bar for a selection bar: a select-all box with the
|
|
79
|
+
count ("2 selected"; indeterminate while only some are selected, a click then
|
|
80
|
+
selects none), Copy (disabled until a block is selected) and Done; Esc leaves
|
|
81
|
+
too. Each row gets a checkbox at the start of its header.
|
|
82
|
+
|
|
83
|
+
An empty zone has no label row: its bar (Paste, and on the first zone Undo,
|
|
84
|
+
Redo and the mode) sits right after its native add button.
|
|
85
|
+
|
|
86
|
+
The plugin adds no side panel of its own for any of this. The Blockscene side
|
|
87
|
+
panel appears only for what has no place in the form: version history (when
|
|
88
|
+
enabled) and layout group problems. With neither, there is no panel.
|
|
49
89
|
|
|
50
90
|
## Gallery
|
|
51
91
|
|
|
52
|
-
The modal
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
The modal is a block browser (80vw wide):
|
|
93
|
+
|
|
94
|
+
- **Sidebar** (collapsible to icons; remembered per browser): All, Recently
|
|
95
|
+
used and Starred with counts, then the typologies present in the zone.
|
|
96
|
+
- **Top bar**: count (`34 blocks`, or `3 of 34` while filtering), search,
|
|
97
|
+
filter menus Tags, Media and Content (checkboxes; several values in one menu
|
|
98
|
+
match any of them, different menus combine), a "Fields" switch that shows
|
|
99
|
+
each block's attribute list on its card, and a columns slider (1 to 5,
|
|
100
|
+
remembered per browser). Active values show as removable chips with "Clear
|
|
101
|
+
all"; the empty state offers a reset.
|
|
102
|
+
- **Cards**, grouped by typology: facet badges (IMAGE, VIDEO, GALLERY, RICH
|
|
103
|
+
TEXT, LIST, DYNAMIC, FORM), a star and an always-visible magnifying glass.
|
|
104
|
+
Clicking the card, or focusing it and pressing Enter, inserts it immediately;
|
|
105
|
+
the magnifying glass opens its preview.
|
|
106
|
+
- The preview **magnifies** the block: the card grows into a large panel over the
|
|
107
|
+
grid (sidebar and top bar stay usable, the grid dims underneath) and shrinks
|
|
108
|
+
back into its card on close (close button, Esc or a click on the dimmed
|
|
109
|
+
grid). The animation is transform/opacity only, about 250 ms, and is skipped
|
|
110
|
+
under `prefers-reduced-motion: reduce`. The panel shows a large live preview
|
|
111
|
+
with Fit / Mobile / Tablet / Desktop widths (scaled to fit, as in the page
|
|
112
|
+
preview) and a compact strip: label, uid, description, badges, fields (on
|
|
113
|
+
demand), the variant choice (see [Insert variants](#insert-variants)), star
|
|
114
|
+
and Insert. A direct card insert uses the first variant, when the block has variants.
|
|
115
|
+
|
|
116
|
+
The magnified preview shows the card's thumbnail at once and fades the live
|
|
117
|
+
page in over it once it has loaded. Live source, first match wins:
|
|
118
|
+
|
|
119
|
+
1. **Block preview URL** (`editor.blockPreviewUrl`, Settings, both versions):
|
|
120
|
+
a plain page per block in a plain iframe, no bridge. Placeholders, URL
|
|
121
|
+
encoded: `{uid}`, `{name}` (after the dot), `{category}` (before the dot),
|
|
122
|
+
`{variant}` (the chosen [insert variant](#insert-variants)'s id, else
|
|
123
|
+
`default`) and `{locale}` (the entry's locale, empty when the type is not
|
|
124
|
+
localized). Example:
|
|
125
|
+
`http://localhost:3026/{locale}/block-preview/{name}/{variant}`. Such a
|
|
126
|
+
fixtures route is usually dev-only: leave the option empty on production.
|
|
127
|
+
2. **Page preview route** (the preview route base URL, else on Strapi 5 the
|
|
128
|
+
native Preview origin + `/block-preview/page`): the same bridge as the
|
|
129
|
+
whole-page preview, sent one block made from the chosen variant over the
|
|
130
|
+
schema defaults, mode `preview`. Read-only: every message except `ready`
|
|
131
|
+
is ignored.
|
|
132
|
+
3. Otherwise the image only.
|
|
133
|
+
|
|
134
|
+
If the live page neither loads nor says `ready` within 8 s, the image stays
|
|
135
|
+
with a short note. The iframe uses the page preview's sandbox
|
|
136
|
+
(`allow-scripts allow-same-origin`) and `referrerpolicy="no-referrer"`.
|
|
137
|
+
|
|
138
|
+
Stars and recently used blocks are stored per admin user on the server
|
|
139
|
+
(`GET`/`PUT /blockscene/me/prefs`, `{ starred, recent }`, at most 200 and 20
|
|
140
|
+
existing component uids).
|
|
141
|
+
|
|
142
|
+
### Taxonomy
|
|
143
|
+
|
|
144
|
+
Facets are read from each component schema and the components it nests (one
|
|
145
|
+
level): media fields (`image`, `video` by allowed types, `gallery` when
|
|
146
|
+
multiple), `richtext` (rich text, blocks or a CKEditor custom field), `list`
|
|
147
|
+
(repeatable component), `dynamic` (a relation, or a uid with query, archive or
|
|
148
|
+
related) and `form` (uid or display name with form or contact).
|
|
149
|
+
|
|
150
|
+
The typology is one of hero, text, media, listing, cards, cta, form and
|
|
151
|
+
layout, guessed from the component name and display name (hero, banner,
|
|
152
|
+
cover; text, rich, quote, title; image, media, video, gallery, carousel; list,
|
|
153
|
+
query, archive, related, posts, projects; card; cta, button, link; form,
|
|
154
|
+
contact; wrapper, column, grid, divider, spacer, section; else text). Override
|
|
155
|
+
it, and add up to 10 tags (1 to 24 characters), in Settings, Blockscene, in
|
|
156
|
+
the plugin config (`components[uid].typology` and `components[uid].tags`) or in
|
|
157
|
+
the component schema ([`pluginOptions.blockscene`](#describing-blocks-and-fields-in-the-schema)).
|
|
158
|
+
A Settings value wins over the config, the config over the schema. The former `category` config key still
|
|
159
|
+
works: it becomes the block's tag when no tags are set.
|
|
60
160
|
|
|
61
161
|
The native "Add a component to <zone>" button of an editable zone opens this
|
|
62
162
|
gallery as well (the click is intercepted in the capture phase; Strapi's
|
|
@@ -66,6 +166,168 @@ label ending with the raw zone name, which Strapi's message carries in every
|
|
|
66
166
|
locale; a zone whose name ends another zone's name (`blocks` and `sub blocks`)
|
|
67
167
|
is the documented limit.
|
|
68
168
|
|
|
169
|
+
### Insert variants
|
|
170
|
+
|
|
171
|
+
A block can offer named presets of its field values, so the editor inserts
|
|
172
|
+
"Hero, dark" instead of the bare schema defaults. Variants come from the
|
|
173
|
+
project's code, never from the Settings page: the plugin config
|
|
174
|
+
`components[uid].variants`, a list of `{ id, label, values }`.
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
// config/plugins.js
|
|
178
|
+
components: {
|
|
179
|
+
'blocks.hero': {
|
|
180
|
+
variants: [
|
|
181
|
+
{ id: 'default', label: 'Default', values: {} },
|
|
182
|
+
{ id: 'dark', label: { en: 'Dark', 'pt-BR': 'Escuro' }, values: {
|
|
183
|
+
title: 'Big launch', theme: 'dark',
|
|
184
|
+
link: { text: 'Read more', url: '/news' },
|
|
185
|
+
items: [{ label: 'One' }, { label: 'Two' }],
|
|
186
|
+
} },
|
|
187
|
+
// A frontend fixture works as is: ids, __component, media and relations are dropped.
|
|
188
|
+
{ id: 'cases', label: 'Cases', values: require('../fixtures/hero/cases.json').data },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **Choosing**: with two or more variants the magnified block shows them as a
|
|
195
|
+
row of buttons; the live preview follows the choice (`{variant}` in the
|
|
196
|
+
block preview URL, or the variant's block sent through the bridge) and
|
|
197
|
+
Insert inserts it. Clicking the card or pressing Enter inserts the first variant.
|
|
198
|
+
The picker opened from a page preview insertion seam offers the same
|
|
199
|
+
choice. A block without variants behaves as before; Settings, Blockscene
|
|
200
|
+
lists each block's variants read-only.
|
|
201
|
+
- **Merge**: `values` is a partial field map over the schema defaults. A
|
|
202
|
+
nested component merges over its own defaults; a repeatable list replaces
|
|
203
|
+
the default list, each item over the item's defaults. The row goes through
|
|
204
|
+
the same form path as any gallery insert (Strapi 5 `addFieldRow`, Strapi 4
|
|
205
|
+
`addComponentToDynamicZone` then the fields' `onChange`), so nothing is
|
|
206
|
+
saved until Save and undo/redo covers it.
|
|
207
|
+
- **Supported values**, checked against the schema once at boot: text-like
|
|
208
|
+
fields (strings, rich text, email, uid, dates as strings), numbers
|
|
209
|
+
(integers, big integers, floats, decimals), booleans, enumerations (one of
|
|
210
|
+
their values), JSON, blocks (an array) and `null`; nested components,
|
|
211
|
+
single or repeatable (up to the attribute's `max`, and 100), each validated
|
|
212
|
+
against its own schema. Media and relation values are dropped with a boot
|
|
213
|
+
warning: their ids belong to one install and are not checked here. `id` and
|
|
214
|
+
`__component` keys are ignored, so a content-API shaped block works.
|
|
215
|
+
Unknown attributes, passwords and Dynamic Zones reject the variant.
|
|
216
|
+
- **Bounds**: `id` is 1 to 40 of `a-z 0-9 - _`, unique per block (it is the
|
|
217
|
+
`{variant}` URL segment); `label` a string of 1 to 60 characters or
|
|
218
|
+
`{ "<locale>": string }`; at most 12 variants per block, 16 KB of values
|
|
219
|
+
per variant and 256 KB for all of them. An invalid variant is left out with
|
|
220
|
+
a warning naming the problem; the block keeps its valid ones.
|
|
221
|
+
|
|
222
|
+
### Starter kits
|
|
223
|
+
|
|
224
|
+
New entries can start from a named set of blocks instead of an empty Dynamic
|
|
225
|
+
Zone. Kits live in project code, per content type. When every editable zone is
|
|
226
|
+
empty, Blockscene opens the kit chooser once; choosing one only changes the
|
|
227
|
+
unsaved form, so every placeholder remains editable and **Start blank** keeps
|
|
228
|
+
the current behaviour.
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
// config/plugins.js
|
|
232
|
+
blockscene: { config: {
|
|
233
|
+
kits: {
|
|
234
|
+
'api::page.page': [{
|
|
235
|
+
id: 'landing',
|
|
236
|
+
label: { en: 'Landing page', 'pt-BR': 'Página de campanha' },
|
|
237
|
+
// Optional. A language code such as "pt" also matches "pt-BR".
|
|
238
|
+
locales: ['en', 'pt-BR'],
|
|
239
|
+
zones: { blocks: [
|
|
240
|
+
{ __component: 'blocks.hero', title: 'Replace this headline' },
|
|
241
|
+
{ __component: 'blocks.rich-text', text: 'Replace this text.' },
|
|
242
|
+
] },
|
|
243
|
+
}],
|
|
244
|
+
},
|
|
245
|
+
} }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Rows use the Content API shape and the same validated values as [insert
|
|
249
|
+
variants](#insert-variants). Schema defaults are merged in, nested components
|
|
250
|
+
work, and configured layout OPEN blocks receive their CLOSE automatically.
|
|
251
|
+
Media and relations are omitted because their ids are installation-specific;
|
|
252
|
+
required ones are called out in the server log and must be completed before
|
|
253
|
+
publishing. Invalid kits, missing required scalar values and kits below or
|
|
254
|
+
above a Dynamic Zone's bounds are ignored with a server warning. The chooser
|
|
255
|
+
is remembered for the current new-entry browser history item, so re-rendering
|
|
256
|
+
or reloading it does not apply or prompt twice.
|
|
257
|
+
|
|
258
|
+
## Row thumbnails
|
|
259
|
+
|
|
260
|
+
Each added block shows its thumbnail at the left of the native accordion
|
|
261
|
+
header, and clicking it opens the image full size in a modal instead of
|
|
262
|
+
opening or closing the row. The sources are the same as the gallery cards,
|
|
263
|
+
minus the wireframe: a block with no image shows nothing, so nothing new is
|
|
264
|
+
invented for blocks that were never captured. The thumbnail is inserted into
|
|
265
|
+
Strapi's own header through the DOM (no Content Manager patch) and is
|
|
266
|
+
restored when Strapi re-renders the list. Configured OPEN and CLOSE group
|
|
267
|
+
markers hide it by default. Override any component with
|
|
268
|
+
`components[uid].showRowThumbnail` in code or on the Settings page, or turn all
|
|
269
|
+
row thumbnails off with the editor option `showRowThumbnails: false`.
|
|
270
|
+
|
|
271
|
+
## Row actions
|
|
272
|
+
|
|
273
|
+
Each block header gets a few icons right before Strapi's own delete button
|
|
274
|
+
(inserted through the DOM, like the thumbnails; read-only rows get none):
|
|
275
|
+
|
|
276
|
+
- **Hide on the site** (eye). The block stays in the document and in the
|
|
277
|
+
admin, dimmed with a "Hidden" badge (also in the page preview, which receives
|
|
278
|
+
`hidden: true` for it). What the content API does is the editor option
|
|
279
|
+
`hiddenBlocks`: `strip` (default) removes hidden rows from Dynamic Zones in
|
|
280
|
+
content-API reads (REST and GraphQL, including zones of populated relations
|
|
281
|
+
and components; admin and Content Manager reads always return every row),
|
|
282
|
+
`flag` sends them with the attribute so the frontend decides, `off` removes
|
|
283
|
+
the eye and strips nothing. On a configured group OPEN the eye hides the
|
|
284
|
+
whole group.
|
|
285
|
+
- **Duplicate**. A deep copy right below: ids removed from the row and its
|
|
286
|
+
nested components, new row key, media kept, relations kept (Strapi 5 reads
|
|
287
|
+
the source row's relations from the server and adds its unsaved changes).
|
|
288
|
+
A group OPEN duplicates with its whole range. Option `duplicate`.
|
|
289
|
+
- **Copy / Paste** across pages. The copy icon copies one block; "Select
|
|
290
|
+
blocks" in the zone bar's "…" menu shows a checkbox per row, then Copy. The
|
|
291
|
+
blocks are stored in this browser's localStorage (`blockscene:clipboard:v1`,
|
|
292
|
+
with the source content type and locale). "Paste N blocks" appears in the
|
|
293
|
+
zone bar's "…" menu of every zone that allows all the copied components, and
|
|
294
|
+
in the page preview seams. Paste is all or nothing: every block must be
|
|
295
|
+
allowed in the target zone, fit its maximum, and groups must be whole;
|
|
296
|
+
otherwise a notice explains why and the form is not changed. Media and relations are kept by id/documentId, so paste
|
|
297
|
+
within the same install. Option `clipboard`.
|
|
298
|
+
- **Confirm delete**. The native delete asks "Delete block …?" first;
|
|
299
|
+
confirming runs Strapi's own removal. On a group marker the dialog says only
|
|
300
|
+
the marker is removed. Option `confirmDelete`.
|
|
301
|
+
|
|
302
|
+
Everything goes through the form: nothing is saved until Save, and undo/redo
|
|
303
|
+
(Strapi 5) covers every action. Strapi 4 supports the same actions; relations
|
|
304
|
+
are copied as the form holds them there (the loaded relations of an opened
|
|
305
|
+
block).
|
|
306
|
+
|
|
307
|
+
**The hidden attribute is a database column.** At register time the plugin adds
|
|
308
|
+
a boolean attribute (like i18n adds `locale`) to every component used in a
|
|
309
|
+
Dynamic Zone: `{ type: 'boolean', default: false, configurable: false }`, so
|
|
310
|
+
Strapi's schema sync creates a `bs_hidden` column in each of those component
|
|
311
|
+
tables on the next start (existing rows read as not hidden). It is never shown
|
|
312
|
+
as an input: the plugin removes it from the components' edit layouts through
|
|
313
|
+
the Content Manager's layout hook (it may be listed in "Configure the view" of
|
|
314
|
+
a component; leave it out of the layout). It is not marked `visible: false`,
|
|
315
|
+
because before Strapi 5.45 the edit view crashes on a value whose attribute the
|
|
316
|
+
admin does not know. It is kept out of the Content-Type Builder instead (the
|
|
317
|
+
plugin wraps the builder's component reads), so saving a component there in
|
|
318
|
+
`strapi develop` never writes it to the component's JSON file. With earlier
|
|
319
|
+
versions (up to 2.0.0-alpha.8, and 1.0.0-alpha.8 on Strapi 4) the builder did
|
|
320
|
+
write it (`"bsHidden": { "type": "boolean", "configurable": false, "default": false }`);
|
|
321
|
+
a file that has it keeps working, and the builder keeps it there on later saves.
|
|
322
|
+
To clean it up, delete that entry from the component JSON by hand (the plugin
|
|
323
|
+
still adds the attribute at startup, so the column and its data stay). To check,
|
|
324
|
+
edit a Dynamic Zone component in the builder (for example its display name),
|
|
325
|
+
save, and diff its JSON file: only your change is there. Rename it with the plugin config
|
|
326
|
+
`hiddenAttribute: 'myName'`, or set `hiddenAttribute: false` to add nothing (no
|
|
327
|
+
eye, nothing stripped). A component that already has an attribute of that name
|
|
328
|
+
with another type is skipped with a warning. Removing the plugin (or setting
|
|
329
|
+
`false`) leaves the column in place with its data; drop it yourself if you want.
|
|
330
|
+
|
|
69
331
|
## Thumbnail priority
|
|
70
332
|
|
|
71
333
|
Each card tries these sources in order and moves on when one fails to load.
|
|
@@ -99,26 +361,108 @@ needed for the gallery to pick them up. Reading requires the
|
|
|
99
361
|
- Wireframe palette (background, surfaces, text, accent) with a live preview
|
|
100
362
|
and "Restore default colors". Colors must be `#RRGGBB`.
|
|
101
363
|
- Component list with the effective source, choose/replace image from the
|
|
102
|
-
Media Library, "Use automatic image"
|
|
364
|
+
Media Library, "Use automatic image", the wireframe template, the gallery
|
|
365
|
+
typology ("Automatic" shows the guess) and tags (comma separated); a group
|
|
366
|
+
OPEN also has its layout grid fields (see [Layout grid](#layout-grid)).
|
|
103
367
|
- Editor preferences: enhancements on/off, visibility of each collective
|
|
104
368
|
button, initial accordion state and the initial block mode.
|
|
369
|
+
- Version history (Strapi 5): module switch, covered content types, retention
|
|
370
|
+
(see "Version history").
|
|
371
|
+
- Content types: every project type with a Dynamic Zone, each with on/off
|
|
372
|
+
(off leaves its edit view fully native), the mode its edit
|
|
373
|
+
views open in ("Default" follows the global preview mode), its visual
|
|
374
|
+
editor sidebar and optionally its own page preview toolbar (see below).
|
|
375
|
+
- "Restore project defaults" (or "Reset to defaults" when the project has no
|
|
376
|
+
code defaults), after a confirmation, deletes what was saved here
|
|
377
|
+
(`DELETE /blockscene/settings`, same permission as saving).
|
|
378
|
+
|
|
379
|
+
### Project defaults from code
|
|
380
|
+
|
|
381
|
+
A project can ship its baseline in the plugin config, with the same shape as
|
|
382
|
+
the stored settings (every key optional):
|
|
383
|
+
|
|
384
|
+
```js
|
|
385
|
+
// config/plugins.js
|
|
386
|
+
module.exports = {
|
|
387
|
+
blockscene: { config: { settings: require('./blockscene.json') } },
|
|
388
|
+
}
|
|
389
|
+
// config/blockscene.json
|
|
390
|
+
{
|
|
391
|
+
"palette": { "accent": "#AA3300" },
|
|
392
|
+
"editor": { "previewMode": "split", "initialState": "open" },
|
|
393
|
+
"components": { "blocks.faq": { "template": "faq", "typology": "text" } },
|
|
394
|
+
"contentTypes": {
|
|
395
|
+
"api::page.page": {
|
|
396
|
+
"sidebarPosition": "right",
|
|
397
|
+
"sidebar": [{ "label": "SEO", "icon": "seo", "open": "drawer", "fields": ["seo"] }]
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The effective settings are the built-in defaults, then the code `settings`,
|
|
404
|
+
then what was saved on the page:
|
|
405
|
+
|
|
406
|
+
- `palette` and `editor`: a saved key wins over the code key by key, except
|
|
407
|
+
an empty `previewUrl` or `blockPreviewUrl`, which means "not set": the code
|
|
408
|
+
value applies. Clearing either field on the page therefore falls back to the
|
|
409
|
+
project default (there is no saved "off" for a URL the code provides; remove
|
|
410
|
+
it from the code settings, or set `previewMode` per content type, to keep a
|
|
411
|
+
type out of the page preview).
|
|
412
|
+
- `components` and `contentTypes`: a saved entry replaces the code entry for
|
|
413
|
+
that uid as a whole (a saved `api::page.page` entry replaces its code
|
|
414
|
+
sidebar too); other uids keep their code values.
|
|
415
|
+
|
|
416
|
+
Saving stores only what differs from the code defaults, so later changes to the
|
|
417
|
+
code reach every key nobody overrode. A document saved before the project had
|
|
418
|
+
code defaults holds every palette and editor key, and those keep winning until
|
|
419
|
+
"Restore project defaults". The code settings are checked at boot with the same
|
|
420
|
+
rules as `PUT`; if they are invalid a warning names the problem and they are
|
|
421
|
+
ignored (boot continues). `GET /blockscene/settings` returns them as
|
|
422
|
+
`projectDefaults` (null when absent or invalid).
|
|
105
423
|
|
|
106
424
|
### Editor preferences
|
|
107
425
|
|
|
108
426
|
| Option | Values | Default |
|
|
109
427
|
| --- | --- | --- |
|
|
110
428
|
| Editor enhancements enabled | on/off | on |
|
|
111
|
-
| Show "
|
|
112
|
-
| Show "
|
|
429
|
+
| Show "Expand all" (`showOpenAll`) | yes/no | yes |
|
|
430
|
+
| Show "Collapse all" (`showCloseAll`) | yes/no | yes |
|
|
431
|
+
| Show block thumbnails in the rows (`showRowThumbnails`) | yes/no | yes |
|
|
113
432
|
| Initial accordion state | all closed / all open / remember | all closed |
|
|
114
|
-
|
|
|
115
|
-
|
|
|
433
|
+
| Load rich-text editors on demand (`lazyEditors`, Strapi 5) | yes/no | yes |
|
|
434
|
+
| Custom fields loaded on demand (`lazyFields`, Strapi 5) | up to 20 uids `plugin::x.y` / `global::x` | `["plugin::ckeditor5.CKEditor"]` |
|
|
435
|
+
| Preview route base URL | full URL or empty | empty: native Preview origin (Strapi 5; none on Strapi 4) |
|
|
436
|
+
| Block preview URL | http(s) URL with placeholders, up to 500 characters, or empty | empty: page preview route, else image |
|
|
437
|
+
| Initial preview mode | form / side by side / preview | form |
|
|
438
|
+
| Confirm before deleting a block (`confirmDelete`) | yes/no | yes |
|
|
439
|
+
| Duplicate on each block (`duplicate`) | yes/no | yes |
|
|
440
|
+
| Copy and Paste (`clipboard`) | yes/no | yes |
|
|
441
|
+
| Blocks hidden on the site (`hiddenBlocks`) | `strip` / `flag` / `off` | `strip` |
|
|
442
|
+
| Friendly field labels (`friendlyLabels`, see [Field labels](#field-labels)) | yes/no | yes |
|
|
443
|
+
| Page preview toolbar (`previewToolbar`, see [Pane toolbar](#pane-toolbar)) | ordered list of `modes`, `history`, `versions`, `devices`, `status`, `actions` | all six (`versions`: Strapi 5) |
|
|
444
|
+
| Page widths (`previewDevices`) | 1 to 8 of `fit`, `mobile`, `tablet`, `desktop`, `{ label, width }` | the four built-in |
|
|
445
|
+
|
|
446
|
+
Every edit view opens in its content type's mode, else the initial preview
|
|
447
|
+
mode. Editors can switch while they edit; the switch is not remembered.
|
|
116
448
|
|
|
117
449
|
The initial state is applied once per document/locale after the blocks
|
|
118
|
-
render; edits, reorders and newly inserted blocks are not re-applied.
|
|
119
|
-
|
|
450
|
+
render; edits, reorders and newly inserted blocks are not re-applied.
|
|
451
|
+
`showOpenAll` and `showCloseAll` shape the zone bar's single toggle (both: it
|
|
452
|
+
toggles; one: only that action; none: no button). Hiding it only hides it;
|
|
453
|
+
native accordion headers keep working.
|
|
120
454
|
|
|
121
|
-
|
|
455
|
+
Lazy editors: a custom field listed in `lazyFields` first shows a read-only
|
|
456
|
+
preview of its HTML (same label, hint and error; scripts, frames, styles, event
|
|
457
|
+
handlers and `javascript:` URLs removed) and mounts the real editor on click,
|
|
458
|
+
Enter/Space or keyboard focus, then puts the caret in it. Once mounted it stays
|
|
459
|
+
for that view; closing and reopening the block shows the preview again.
|
|
460
|
+
Disabled fields only show the preview. The wrapper is installed on every
|
|
461
|
+
registered custom field at boot and passes the others straight through; until
|
|
462
|
+
the catalog loads, and when the plugin or the content type is off, every field
|
|
463
|
+
is native.
|
|
464
|
+
|
|
465
|
+
"Remember" stores only the last explicit Expand all / Collapse all click, as
|
|
122
466
|
`open`/`closed` in the browser's localStorage, keyed by admin URL, user id,
|
|
123
467
|
content type and zone. Another user in the same browser does not inherit it.
|
|
124
468
|
Blocked storage or an invalid value falls back to all closed. No sync between
|
|
@@ -128,20 +472,77 @@ The accordion controls drive the native Dynamic Zone accordions through their
|
|
|
128
472
|
`aria-expanded` headers, so no Strapi source is patched. They are validated on
|
|
129
473
|
the two supported versions only.
|
|
130
474
|
|
|
131
|
-
## Whole-page preview
|
|
475
|
+
## Whole-page preview
|
|
132
476
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
477
|
+
Both distributions add a page preview with three modes, chosen from the
|
|
478
|
+
first zone's bar and from the pane toolbar: Fields only (form), Fields + preview (side
|
|
479
|
+
by side: resizable pane, native actions moved to a bar above the form) and
|
|
480
|
+
Visual editor. It posts the live values of the first Dynamic Zone to a page
|
|
136
481
|
your frontend serves; the page renders them with its own components and styles
|
|
137
482
|
and can ask the admin to open a block, edit a field or pick media. Nothing is
|
|
138
483
|
saved or published by the preview.
|
|
139
484
|
|
|
485
|
+
Side by side, hovering a zone row in the form (open or closed) outlines its
|
|
486
|
+
block in the page, and hovering a block in the page outlines its form row in the
|
|
487
|
+
theme's primary color; hovering never scrolls. When that row is outside the
|
|
488
|
+
visible form, an arrow at the top or bottom edge of the form scrolls to it. The
|
|
489
|
+
divider between form and page drags to resize, takes the arrow keys, Home and
|
|
490
|
+
End, and a double click resets it to half.
|
|
491
|
+
|
|
492
|
+
### Visual editor sidebar
|
|
493
|
+
|
|
494
|
+
Per content type, a bar of buttons on the left, right or bottom edge of the
|
|
495
|
+
visual editor. Each button (label, optional icon) opens some of the document's
|
|
496
|
+
own fields in a modal or a drawer: the native form itself, with every other
|
|
497
|
+
field hidden, so components, repeatable components, media, relations and custom
|
|
498
|
+
fields all keep working. Done, Esc or the backdrop put the form back.
|
|
499
|
+
|
|
500
|
+
```js
|
|
501
|
+
// Settings, Blockscene, Content types (or PUT /blockscene/settings, or the code `settings`):
|
|
502
|
+
contentTypes: {
|
|
503
|
+
'api::page.page': {
|
|
504
|
+
sidebarPosition: 'left', // left | right | bottom
|
|
505
|
+
sidebar: [
|
|
506
|
+
{ label: 'Title', icon: 'text', open: 'modal', fields: ['title'] },
|
|
507
|
+
{ label: 'SEO', icon: 'seo', open: 'drawer', fields: ['pageSeo'] },
|
|
508
|
+
],
|
|
509
|
+
},
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Icons: text, tag, seo, settings, image, link, palette, list, globe, info (or
|
|
514
|
+
none). Fields are the type's own top-level attributes; an item naming a field
|
|
515
|
+
removed from the schema is dropped on read. Up to 12 items.
|
|
516
|
+
|
|
517
|
+
The Settings page edits it under each enabled content type: position, then per
|
|
518
|
+
item the label, icon, drawer or modal, the fields (checkboxes), move up/down and
|
|
519
|
+
remove. An item without a label or without fields is flagged and blocks Save.
|
|
520
|
+
Removing every item removes the override.
|
|
521
|
+
|
|
522
|
+
The pane toolbar sets the page width: Fit (the pane), Mobile (390 px), Tablet
|
|
523
|
+
(834 px) or Desktop (1440 px). A device renders at its own width and scales down
|
|
524
|
+
when the pane is narrower, so the desktop layout fits side by side; switching
|
|
525
|
+
never reloads the page. The choice is remembered per browser.
|
|
526
|
+
|
|
140
527
|
Minimal configuration:
|
|
141
528
|
|
|
142
529
|
1. Serve a page that implements the bridge. `examples/page-preview/index.html`
|
|
143
530
|
is a framework-free reference: it answers `ready`/`ping`, renders
|
|
144
531
|
`update-page`, and sends `select`, `focus`, `edit`, `media`, `media-remove`.
|
|
532
|
+
Hover sync is optional: the page sends `{ type: 'hover', key }` (a block
|
|
533
|
+
key, or `null` off blocks) when it changes, and marks the block of the
|
|
534
|
+
admin's `{ type: 'hover', key }` without scrolling. A page that ignores it
|
|
535
|
+
keeps working.
|
|
536
|
+
Row actions: a block hidden on the site arrives with `hidden: true` (the
|
|
537
|
+
example dims it), and `update-page` carries `clipboard` (the number of copied
|
|
538
|
+
blocks, 0 when none or not editable); while it is positive the page may show
|
|
539
|
+
a Paste button in its seams that sends `{ type: 'paste', after }` (a block
|
|
540
|
+
key, or `null` for the start). Both are optional.
|
|
541
|
+
`update-page` also carries the complete live `entry`, including unsaved
|
|
542
|
+
values, plus `contentType` metadata and the projected zone name. New schema
|
|
543
|
+
fields therefore reach the frontend automatically; the frontend decides how
|
|
544
|
+
each field affects its page. The normalized `blocks` array remains alongside
|
|
545
|
+
it for visual editing and block-level actions.
|
|
145
546
|
Keep the origin check (`admin` query parameter or your own constant).
|
|
146
547
|
2. Allow the admin origin to embed it (`Content-Security-Policy: frame-ancestors`)
|
|
147
548
|
and allow the page origin in the Strapi admin CSP (`frame-src`).
|
|
@@ -151,23 +552,148 @@ Minimal configuration:
|
|
|
151
552
|
Use the same host name the frontend dev server was started for
|
|
152
553
|
(`localhost` vs `127.0.0.1`): Next 16 refuses its dev WebSocket for the
|
|
153
554
|
other one and the page then never hydrates, so `ready` never arrives and
|
|
154
|
-
the
|
|
555
|
+
the pane reports the preview as unavailable with no error in either console.
|
|
155
556
|
|
|
156
|
-
The bridge only exposes what the schema
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
557
|
+
The bridge only exposes what the schema and the current user's read permissions
|
|
558
|
+
allow. Attributes marked with Strapi's `private: true`, passwords, and
|
|
559
|
+
attributes with `pluginOptions.blockscene.private: true` are never projected,
|
|
560
|
+
focused or edited; the Blockscene option is useful for keeping a field out of
|
|
561
|
+
the preview without changing its public API behavior. This privacy rule also
|
|
562
|
+
applies inside components. Media URLs are passed through for any http(s) host
|
|
563
|
+
(S3/CDN providers included) and same-origin paths; the zone shown is the first
|
|
564
|
+
Dynamic Zone the user may read, and editing additionally needs the update
|
|
565
|
+
permission on it. Pending dialogs are dropped when the document or locale
|
|
566
|
+
changes.
|
|
162
567
|
|
|
163
568
|
Editing from the page: plain-text areas the page explicitly maps
|
|
164
|
-
(`data-block-field` + `contenteditable="plaintext-only"`) send `edit
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
569
|
+
(`data-block-field` + `contenteditable="plaintext-only"`) send `edit`. In the
|
|
570
|
+
visual editor, a click on any other field or on the block opens the whole block
|
|
571
|
+
over the page: its own native form (every field type, CKEditor, media, nested
|
|
572
|
+
and repeatable components), with the clicked field focused and outlined. The
|
|
573
|
+
page updates while you type; Done, Esc or the backdrop put the block back. Side
|
|
574
|
+
by side focuses the field in the form on the left instead. Media
|
|
168
575
|
fields open the native Media Library. Everything is validated against the schema
|
|
169
576
|
and the zone's edit permission; a published page never receives unsaved values
|
|
170
|
-
by itself.
|
|
577
|
+
by itself.
|
|
578
|
+
|
|
579
|
+
On Strapi 4 (run on 4.26.1; the edit view APIs it uses are the same in 4.11.0)
|
|
580
|
+
the page preview is the same: the three modes, the same bridge
|
|
581
|
+
(`blockscene:page-preview:v1`, so one frontend route serves both), widths,
|
|
582
|
+
hover sync, the block dialog, the visual editor sidebar, custom panels,
|
|
583
|
+
insertion, groups and paste from the page, and the per content type settings.
|
|
584
|
+
It reads and writes the edit view's own data (`useCMEditViewDataManager`); new
|
|
585
|
+
blocks use the native insertion at a position. Differences:
|
|
586
|
+
|
|
587
|
+
- No undo and redo (no History buttons, no shortcuts). Strapi 4's form has no
|
|
588
|
+
way to set all values at once, and restoring each field would also undo the
|
|
589
|
+
relations it loads lazily, which Save would then disconnect.
|
|
590
|
+
- No native Preview to fall back on: set the preview route base URL.
|
|
591
|
+
- The toolbar's status shows Draft or Published (Strapi 4 has no Modified
|
|
592
|
+
state) and the unsaved hint; its Save and Publish (or Unpublish) click the
|
|
593
|
+
edit view header's own buttons, so their checks and dialogs are Strapi's.
|
|
594
|
+
- The browser smoke covers split mode, select, inline and live edits, widths,
|
|
595
|
+
hover, the block dialog, a sidebar item, media, insertion and toolbar Save
|
|
596
|
+
on 4.26.1; group tools, paste and custom panels from the page share the
|
|
597
|
+
Strapi 5 code and are checked there only.
|
|
598
|
+
- With Strapi 4's two navigation columns the content area is narrower: the
|
|
599
|
+
form keeps at least 520 px, so side by side the page gets less than half on
|
|
600
|
+
a 1440 px screen.
|
|
601
|
+
|
|
602
|
+
### Pane toolbar
|
|
603
|
+
|
|
604
|
+
Which controls the toolbar shows, and in which order, is `previewToolbar`:
|
|
605
|
+
`modes` (Fields only, Fields + preview, Visual editor), `history` (Undo, Redo),
|
|
606
|
+
`versions` (the version history drawer, Strapi 5),
|
|
607
|
+
`devices` (the width menu), `status` (Draft/Modified/Published and the unsaved
|
|
608
|
+
hint) and `actions` (Save, Publish). A missing id is hidden; `status` and
|
|
609
|
+
`actions` stay one group on the right, placed where the first of them is
|
|
610
|
+
listed. The preview's loading or error notice always shows. Without `modes` the
|
|
611
|
+
pane stays in the mode the edit view opened in (for example a visual editor
|
|
612
|
+
only view with `previewMode: 'preview'`); the form's zone bar keeps its own
|
|
613
|
+
Undo, Redo and mode menu.
|
|
614
|
+
|
|
615
|
+
`previewDevices` lists the width menu's entries in order; the first is used
|
|
616
|
+
until an editor picks another. Besides the four names, `{ label, width }` adds
|
|
617
|
+
a custom width (label up to 24 characters, width a whole number from 240 to
|
|
618
|
+
3840 px; no name or width twice; up to 8 entries). With a single entry the menu
|
|
619
|
+
is hidden.
|
|
620
|
+
|
|
621
|
+
```js
|
|
622
|
+
// Settings, Blockscene (Editor preferences, or per content type), PUT /blockscene/settings, or the code `settings`:
|
|
623
|
+
editor: {
|
|
624
|
+
previewToolbar: ['modes', 'devices', 'actions'],
|
|
625
|
+
previewDevices: ['fit', 'mobile', { label: 'Laptop', width: 1280 }, 'desktop'],
|
|
626
|
+
},
|
|
627
|
+
contentTypes: {
|
|
628
|
+
'api::landing.landing': { previewToolbar: ['actions'], previewDevices: ['desktop'] },
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
A content type's value replaces the global one. The Settings page edits both as
|
|
633
|
+
checklists (check to show, arrows to order, "Add a width" for custom widths);
|
|
634
|
+
"Own page preview toolbar" under a content type starts as a copy of the global
|
|
635
|
+
choice, and turning it off goes back to the global one.
|
|
636
|
+
|
|
637
|
+
### Custom sidebar panels
|
|
638
|
+
|
|
639
|
+
A project (in its `src/admin/app.tsx`) or another plugin can add its own panel
|
|
640
|
+
to the visual editor sidebar. Registered panels come after the configured field
|
|
641
|
+
items, in registration order; the rail shows in the Visual editor mode as soon
|
|
642
|
+
as there is one item.
|
|
643
|
+
|
|
644
|
+
```tsx
|
|
645
|
+
// src/admin/app.tsx
|
|
646
|
+
const Notes = ({ values, onChange, disabled, close }) => (
|
|
647
|
+
<textarea value={values.notes ?? ''} disabled={disabled}
|
|
648
|
+
onChange={(e) => onChange('notes', e.target.value)} />
|
|
649
|
+
)
|
|
650
|
+
export default {
|
|
651
|
+
bootstrap(app) {
|
|
652
|
+
app.getPlugin('blockscene')?.apis.registerPanel({
|
|
653
|
+
id: 'notes', // 1 to 40 letters, digits, _ or -; the same id again replaces the panel
|
|
654
|
+
label: 'Notes', // 1 to 40 characters
|
|
655
|
+
icon: 'info', // optional: an icon name (the sidebar icons above, for example) or a React element
|
|
656
|
+
open: 'drawer', // 'drawer' (default) or 'modal'
|
|
657
|
+
contentTypes: ['api::page.page'], // optional: only these types (default: every type)
|
|
658
|
+
Component: Notes,
|
|
659
|
+
})
|
|
660
|
+
},
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
`Component` receives `model` (content type uid), `documentId` (undefined while
|
|
665
|
+
creating), `locale`, `values` (the live form values), `onChange(name, value)`
|
|
666
|
+
(the form's own setter: the change is unsaved, goes through undo and redo, and
|
|
667
|
+
Save or Publish store it with the usual permission checks), `disabled` (the form
|
|
668
|
+
is read-only) and `close()`. It renders inside the Content Manager edit view, so
|
|
669
|
+
it can also use the admin's hooks (`useForm`, `useFetchClient`...). Strapi runs
|
|
670
|
+
every plugin's `register` before any `bootstrap`, then the host's `bootstrap`:
|
|
671
|
+
calling `registerPanel` from either bootstrap works. An invalid panel is logged
|
|
672
|
+
(`[blockscene] registerPanel: ...`) and ignored; `registerPanel` returns `true`
|
|
673
|
+
or `false`. A panel that throws while rendering shows the plugin's error notice
|
|
674
|
+
in its drawer; the editor keeps working. On Strapi 4 the same call works (its
|
|
675
|
+
`values` and `onChange` are the edit view data manager's); the lab smoke covers
|
|
676
|
+
panels on Strapi 5 only.
|
|
677
|
+
|
|
678
|
+
### Undo and redo
|
|
679
|
+
|
|
680
|
+
The first zone's bar and the pane toolbar have Undo and Redo buttons (Strapi 5),
|
|
681
|
+
on one shared history (the shortcuts use it too).
|
|
682
|
+
They step through the whole edit view: blocks inserted, removed or moved from the
|
|
683
|
+
gallery, the page, groups or the native actions, and any field edit. Changes
|
|
684
|
+
within 400 ms of each other are one step, so a burst of typing undoes at once;
|
|
685
|
+
a block dragged with the mouse is one step however long the drag lasts; up to
|
|
686
|
+
100 steps. Shortcuts: Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z and Ctrl+Y, only while
|
|
687
|
+
the focus is outside a text field, select or rich text editor (those keep their
|
|
688
|
+
own undo).
|
|
689
|
+
|
|
690
|
+
Limits: the history lives in the open edit view and starts over when the
|
|
691
|
+
document, the locale or its loaded values change, so a Save clears it. Shortcuts
|
|
692
|
+
pressed inside the preview page are not seen (it is another origin); use the
|
|
693
|
+
buttons there. A custom field that reads its value only once on mount keeps
|
|
694
|
+
showing the old text after an undo until the view reloads; the CKEditor 5
|
|
695
|
+
custom field (@_sh/strapi-plugin-ckeditor) follows the restored value (checked
|
|
696
|
+
on Strapi 5.31).
|
|
171
697
|
|
|
172
698
|
## Layout groups (optional)
|
|
173
699
|
|
|
@@ -192,7 +718,8 @@ pairs naming unknown components are dropped. Several OPENs may share one CLOSE.
|
|
|
192
718
|
With a valid map:
|
|
193
719
|
|
|
194
720
|
- 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.
|
|
721
|
+
same unsaved change: an empty group, never a lone marker. A CLOSE is not
|
|
722
|
+
offered on its own in the gallery or the preview picker. When the CLOSE is
|
|
196
723
|
not allowed in that zone, or the zone has no room for two rows, nothing is
|
|
197
724
|
inserted and a notice explains why (two form dispatches in one React batch;
|
|
198
725
|
no partial state is rendered or saved). Children are added
|
|
@@ -204,7 +731,7 @@ With a valid map:
|
|
|
204
731
|
groups may nest; a CLOSE right after its OPEN is a valid empty group. Errors
|
|
205
732
|
are `closeBeforeOpen` (CLOSE with no open group), `mismatch` (CLOSE is not the
|
|
206
733
|
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
|
|
734
|
+
- Nothing is repaired on read: the Blockscene side panel lists the problems (row, marker) and
|
|
208
735
|
the stray rows stay visible so the editor can fix them. Drafts save normally.
|
|
209
736
|
- Publishing is refused server-side while a configured pair is unbalanced.
|
|
210
737
|
Strapi 5: a document-service middleware intercepts every facade path that
|
|
@@ -228,6 +755,437 @@ With a valid map:
|
|
|
228
755
|
Configured groups are stored exactly as before: the flat list of components in
|
|
229
756
|
the zone. Removing the config only removes the group tools and the guard.
|
|
230
757
|
|
|
758
|
+
### Groups in the form
|
|
759
|
+
|
|
760
|
+
The edit view draws the same structure over Strapi's native rows (Strapi 5 and
|
|
761
|
+
4; data attributes on the rows, nothing of the Content Manager is patched):
|
|
762
|
+
|
|
763
|
+
- The rows between an OPEN and its CLOSE are indented under it with a guide line
|
|
764
|
+
in the admin's primary colour, one level (and one line) per nested group.
|
|
765
|
+
- The CLOSE row reads as the group's end: compact, dimmed, without a drag handle
|
|
766
|
+
(it cannot be dragged on its own; its delete stays, with the marker notice).
|
|
767
|
+
- Each closed group's OPEN header gets a chevron before its title. It folds the
|
|
768
|
+
group: its rows and CLOSE are hidden and the header shows "N blocks" (every
|
|
769
|
+
row inside, nested ones too, CLOSE markers excluded). Folding is kept in memory
|
|
770
|
+
for the session, per document, zone and group; a reload shows every group open.
|
|
771
|
+
A validation error inside a folded group (on Save or Publish) unfolds it.
|
|
772
|
+
Expand all / Collapse all act on the visible block accordions only: they never
|
|
773
|
+
open the forms of a folded group's rows, and a group keeps its own chevron.
|
|
774
|
+
- Moving an OPEN moves its group. Nothing intercepts Strapi's drag and drop:
|
|
775
|
+
after any change of the zone, if the only thing that changed is one OPEN
|
|
776
|
+
moving, its members as they were (children and CLOSE) are put back right after
|
|
777
|
+
it, in the same unsaved form change. This covers the mouse (settled on drop:
|
|
778
|
+
Strapi moves the row on every hover), the keyboard (Space, arrows, Space on the
|
|
779
|
+
drag handle) and the move arrows of small screens. An OPEN dropped among its
|
|
780
|
+
own rows (a single keyboard or arrow step down) moves the group one row down
|
|
781
|
+
instead. The whole drag and the correction are one undo step (Strapi 5).
|
|
782
|
+
- Everything else keeps the flat-list meaning: a child dragged out of the range
|
|
783
|
+
is an ordinary block from then on, a block dropped inside becomes a child, and
|
|
784
|
+
an OPEN dropped inside another group is nested there with its rows. A CLOSE
|
|
785
|
+
moved by other means (the small-screen arrows) ends the group where it lands.
|
|
786
|
+
Unclosed groups and stray markers are indented as the page preview shows them
|
|
787
|
+
but have no chevron and are never moved (the diagnostics list them).
|
|
788
|
+
|
|
789
|
+
### Layout grid
|
|
790
|
+
|
|
791
|
+
An OPEN whose component lays its children out in columns (a "grid columns"
|
|
792
|
+
wrapper) can show them that way in the form. Name the OPEN attribute that holds
|
|
793
|
+
its desktop column count, per component, in Settings (the OPEN's card, "Layout
|
|
794
|
+
grid") or in the code `settings`:
|
|
795
|
+
|
|
796
|
+
```json
|
|
797
|
+
{ "components": { "wrappers.grid-columns": { "layout": { "columnsField": "columnsMd", "mobileColumnsField": "columns", "maxColumns": 3 } } } }
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
- `columnsField` (required) and `mobileColumnsField` (optional) must be
|
|
801
|
+
attributes of that component that can hold a count: `integer`, `biginteger`,
|
|
802
|
+
`float`, `decimal`, `string`, or an `enumeration` whose every value is a whole
|
|
803
|
+
number (`["1", "2", "3"]`). `maxColumns` is 1 to 12 (default 12). Checked on
|
|
804
|
+
`PUT` and on the code settings at boot; a saved layout whose attribute was
|
|
805
|
+
removed is dropped on read. A layout on a component that is not a configured
|
|
806
|
+
OPEN does nothing.
|
|
807
|
+
- The count is read live from the OPEN's form: its value, else the attribute's
|
|
808
|
+
default, else 1, capped at `maxColumns` (a value that is not a whole number
|
|
809
|
+
from 1 counts as missing). The grid shows the desktop count; the mobile one is
|
|
810
|
+
named in its caption.
|
|
811
|
+
- Each child is a cell under the OPEN's header (its name and the first text of
|
|
812
|
+
its fields); a nested group is one cell ("N blocks"); the CLOSE stays a row
|
|
813
|
+
below. The children's own rows are hidden but stay mounted, so the form,
|
|
814
|
+
validation, Save and the row tools keep working on them; Expand all skips
|
|
815
|
+
them as it skips a folded group.
|
|
816
|
+
- A cell opens its block's native form in the block dialog (the one of the
|
|
817
|
+
visual editor), in any mode, with every field type. A validation error inside
|
|
818
|
+
a child gives its cell a red border; the dialog shows the field.
|
|
819
|
+
- Reorder by dragging a cell onto another, or with Alt+Arrow keys on a focused
|
|
820
|
+
cell (left/right one cell, up/down one row of the grid); nested groups move
|
|
821
|
+
whole. "Remove from group" in a cell's "…" menu moves the block right after
|
|
822
|
+
the CLOSE. "Add a block" opens the gallery (the zone's allowed components) and
|
|
823
|
+
inserts at the end of the group; an OPEN brings its CLOSE. Each of these is
|
|
824
|
+
one form change: one undo step on Strapi 5, nothing saved before Save.
|
|
825
|
+
- The icon on the OPEN's header switches that group between grid and list (the
|
|
826
|
+
native rows) for the session, per document and group. A folded group shows no
|
|
827
|
+
grid.
|
|
828
|
+
- Dragging a cell out of the grid onto the list is not supported (it would need
|
|
829
|
+
Strapi's own drag and drop): use "Remove from group", or the list view.
|
|
830
|
+
|
|
831
|
+
Strapi 5 and 4 (the block dialog is shared by both since the page preview core).
|
|
832
|
+
|
|
833
|
+
## Field labels
|
|
834
|
+
|
|
835
|
+
The Content Manager shows each field under its "Configure the view" label, which
|
|
836
|
+
defaults to the raw attribute name (`mobileColumnsCount`). Blockscene rewrites
|
|
837
|
+
the edit view labels, hints and placeholders through the Content Manager's own
|
|
838
|
+
`Admin/CM/pages/EditView/mutate-edit-view-layout` hook (Strapi 5 and 4, the
|
|
839
|
+
same hook the i18n plugin uses), so nothing in the DOM is patched and the
|
|
840
|
+
saved view configuration is never changed. Per field, the first that applies:
|
|
841
|
+
|
|
842
|
+
1. **Project texts** from the plugin config `fields` (below);
|
|
843
|
+
2. **schema metadata**: `pluginOptions.blockscene` on the attribute, with its
|
|
844
|
+
translations (see [Describing blocks and fields in the schema](#describing-blocks-and-fields-in-the-schema));
|
|
845
|
+
3. **a label set in "Configure the view"** (anything other than the attribute name);
|
|
846
|
+
4. **the humanized name** when "Friendly field labels" is on: `mobileColumnsCount`
|
|
847
|
+
reads "Mobile columns count", `page_seo` "Page seo", acronym runs stay
|
|
848
|
+
(`pageSEO` "Page SEO", `ctaURL` "Cta URL").
|
|
849
|
+
|
|
850
|
+
Description (the hint under the field) and placeholder change only when the
|
|
851
|
+
project texts have them. Texts follow the admin user's interface language
|
|
852
|
+
(`strapi-admin-language`, else English), resolved as: exact locale, then its
|
|
853
|
+
language (`pt-BR` → `pt`), then any variant of that language, then `en`, then
|
|
854
|
+
the first value.
|
|
855
|
+
|
|
856
|
+
```js
|
|
857
|
+
// config/plugins.js
|
|
858
|
+
blockscene: { config: { fields: {
|
|
859
|
+
'api::page.page': {
|
|
860
|
+
pageSeo: { label: { en: 'Search engine settings', 'pt-BR': 'Configurações de SEO' },
|
|
861
|
+
description: { en: 'Title and description shown on Google', 'pt-BR': 'Título e descrição no Google' } },
|
|
862
|
+
},
|
|
863
|
+
'shared.seo': { metaTitle: { placeholder: 'Up to 60 characters' },
|
|
864
|
+
metaDescription: { help: { en: 'Shown under the title in search results. Google cuts it around 155 characters; write for people, not keywords.' } } },
|
|
865
|
+
} } },
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
Keys are content type or component uids, then attribute names; each of
|
|
869
|
+
`label` (up to 80 characters), `description` (300), `placeholder` (120) and
|
|
870
|
+
`help` (500) is a string or `{ "<locale>": string }`.
|
|
871
|
+
|
|
872
|
+
`description` stays the line under the input. `help` is for the longer
|
|
873
|
+
explanation that would stretch the form: on Strapi 5 it becomes an "i" icon next
|
|
874
|
+
to the label, in the Content Manager's own label action slot (where the i18n
|
|
875
|
+
plugin puts its globe; both icons show on localized fields), with the text in a
|
|
876
|
+
tooltip on hover and on keyboard focus (the button is reachable with Tab and
|
|
877
|
+
named "More information"). A field can have both. On Strapi 4 the Content
|
|
878
|
+
Manager gives component fields, where blocks live, no label action, so `help`
|
|
879
|
+
is appended to the description under the input instead. The map is validated at boot: an unknown
|
|
880
|
+
key, an empty or too long text, or a malformed locale code logs a warning
|
|
881
|
+
naming it and the whole `fields` config is ignored (labels fall back to the two
|
|
882
|
+
other steps). Entries for a uid or attribute the schema no longer has (a
|
|
883
|
+
renamed or removed field) are skipped with one warning listing them; the rest
|
|
884
|
+
keep working. It is code only, versioned with the
|
|
885
|
+
project; the Settings page has the on/off switch but does not edit texts.
|
|
886
|
+
|
|
887
|
+
To start the file, generate every field of a project with its humanized English
|
|
888
|
+
label from a clone of this repository, then keep what you want to change:
|
|
889
|
+
|
|
890
|
+
```bash
|
|
891
|
+
node scripts/fields-skeleton.mjs ../my-project/backend > ../my-project/backend/config/blockscene-fields.json
|
|
892
|
+
# config/plugins.js: fields: require('./blockscene-fields.json')
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
Limits: the hook runs synchronously and reads the last catalog the admin
|
|
896
|
+
loaded. The first edit or list view of a session starts that request and shows
|
|
897
|
+
native labels; the next layout computed (another document, list → edit, a
|
|
898
|
+
locale switch) has them. Strapi 5 gives the hook no content type uid, so the
|
|
899
|
+
layout is matched by display name and field names; two content types that
|
|
900
|
+
share both are left untouched (component fields always work: they are keyed by
|
|
901
|
+
uid). The switch "Editor enhancements enabled", `BLOCKSCENE_DISABLED` and a
|
|
902
|
+
content type turned off all leave the native labels; any error leaves the
|
|
903
|
+
layout untouched. Changing the interface language applies from the next edit
|
|
904
|
+
view.
|
|
905
|
+
|
|
906
|
+
## Describing blocks and fields in the schema
|
|
907
|
+
|
|
908
|
+
The same texts can live in the component and content type JSON files, next to
|
|
909
|
+
what they describe, instead of the long `components` and `fields` config maps:
|
|
910
|
+
|
|
911
|
+
```jsonc
|
|
912
|
+
// src/components/sections/rich-text.json
|
|
913
|
+
{
|
|
914
|
+
"collectionName": "components_sections_rich_texts",
|
|
915
|
+
"info": { "displayName": "Rich text", "description": "Formatted text with an optional title and anchor." },
|
|
916
|
+
"pluginOptions": { "blockscene": { "tags": ["Editorial"] } },
|
|
917
|
+
"attributes": {
|
|
918
|
+
"anchor": { "type": "string", "pluginOptions": { "blockscene": { "help": "Id for links to this block (#id), without the \"#\"." } } },
|
|
919
|
+
"text": { "type": "richtext" }
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// src/components/shared/tag.json, attribute "label"
|
|
923
|
+
"label": { "type": "string", "pluginOptions": { "blockscene": { "label": "Tag text", "help": "One or two words." } } }
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
- **Block**: the native `info.description` is the gallery description;
|
|
927
|
+
`pluginOptions.blockscene` takes `label`, `typology`, `tags`, `keywords` and
|
|
928
|
+
`image`, with the same rules as the `components` config entries.
|
|
929
|
+
- **Field** (any content type or component attribute): `pluginOptions.blockscene`
|
|
930
|
+
takes `label` (up to 80 characters), `description` (300), `placeholder` (120)
|
|
931
|
+
and `help` (500), each a plain string in the source language, English.
|
|
932
|
+
|
|
933
|
+
Other languages go in one flat file per locale, given to the plugin config:
|
|
934
|
+
|
|
935
|
+
```js
|
|
936
|
+
// config/plugins.js
|
|
937
|
+
blockscene: { config: { translations: { 'pt-BR': require('./blockscene/pt-BR.json') } } },
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
```json
|
|
941
|
+
{
|
|
942
|
+
"sections.rich-text": "Texto formatado",
|
|
943
|
+
"sections.rich-text.description": "Texto formatado com título e âncora opcionais.",
|
|
944
|
+
"sections.rich-text.anchor": "Âncora",
|
|
945
|
+
"sections.rich-text.anchor.help": "Id para links a este bloco (#id), sem o \"#\".",
|
|
946
|
+
"shared.tag.label": "Texto da tag"
|
|
947
|
+
}
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
Keys are `<uid>` (block label, else the `displayName`), `<uid>.description`
|
|
951
|
+
(block description), `<uid>.<attribute>` (field label) and
|
|
952
|
+
`<uid>.<attribute>.<description|placeholder|help>`. A component attribute named
|
|
953
|
+
`description` takes its label as `<uid>.description.label`. The admin picks the
|
|
954
|
+
language as for the field labels above; a missing key shows the schema's
|
|
955
|
+
English text, and a translated field label with no English one in the schema
|
|
956
|
+
shows Strapi's own label in English.
|
|
957
|
+
|
|
958
|
+
Precedence, per text: the plugin config (`components`, `fields`, and Settings
|
|
959
|
+
for typology and tags) > schema metadata > native Strapi ("Configure the view"
|
|
960
|
+
labels, `displayName`) > automatic (humanized names, guessed typology). The
|
|
961
|
+
existing config keeps working unchanged. Everything is read once at boot:
|
|
962
|
+
invalid values (a typology outside the list, a label over 80 characters, an
|
|
963
|
+
unknown key) and translation keys naming no known block or field are left out,
|
|
964
|
+
with one warning listing them by uid.
|
|
965
|
+
|
|
966
|
+
**Content-Type Builder.** Tested on Strapi 5.52.1 and 4.26.1 in development
|
|
967
|
+
mode: editing and saving a component in the Content-Type Builder keeps
|
|
968
|
+
`pluginOptions.blockscene` (block and attributes) and `info.description`. It
|
|
969
|
+
has no field for them, so add and change them in the JSON files.
|
|
970
|
+
|
|
971
|
+
To move an existing config, from a clone of this repository (idempotent; each
|
|
972
|
+
schema file keeps its indentation and key order; labels equal to what the
|
|
973
|
+
plugin would show anyway, the humanized name or the `displayName`, are dropped):
|
|
974
|
+
|
|
975
|
+
```bash
|
|
976
|
+
node scripts/schema-metadata.mjs ../my-project/backend --fields fields.json --components components.json [--out dir] [--source en] [--dry-run]
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
`fields.json` and `components.json` hold the two config maps as JSON. The
|
|
980
|
+
translation files go to `config/blockscene/<locale>.json`; the script lists
|
|
981
|
+
what it could not place and the `components` keys that stay in the config
|
|
982
|
+
(`variants`, `template`). Remove the migrated entries from the config
|
|
983
|
+
afterwards: the config wins.
|
|
984
|
+
|
|
985
|
+
## Version history (Strapi 5)
|
|
986
|
+
|
|
987
|
+
Off by default. Turn it on in Settings, Blockscene, "Version history" (or from
|
|
988
|
+
code, `settings.history`, see below). Strapi Community has no content history
|
|
989
|
+
(it is a Growth feature); this module records one event per content change and
|
|
990
|
+
keeps a snapshot of the content, so an editor can see what changed and load an
|
|
991
|
+
earlier version back into the form.
|
|
992
|
+
|
|
993
|
+
**What it captures.** Every create, save, publish, unpublish, discard draft and
|
|
994
|
+
delete of the covered content types (all `api::` types by default, or a list)
|
|
995
|
+
made through Strapi's document service: the admin (single and bulk actions),
|
|
996
|
+
the REST and GraphQL APIs, and any plugin or custom code that uses
|
|
997
|
+
`strapi.documents`. One event per affected locale, with who (admin user, API
|
|
998
|
+
token, Users & Permissions user, or `system`), when, the action and a summary
|
|
999
|
+
(blocks added, removed and changed per Dynamic Zone, top-level fields changed)
|
|
1000
|
+
against the previous snapshot of the same locale. The snapshot is the stored
|
|
1001
|
+
entry with components and Dynamic Zones in full, relations as documentIds and
|
|
1002
|
+
media as file ids (a 34-block page is a few KB; the capture adds a few
|
|
1003
|
+
milliseconds to a save). A snapshot identical to the previous one is not stored
|
|
1004
|
+
again (the event is, with the same hash).
|
|
1005
|
+
|
|
1006
|
+
**What it does not see.** Writes that bypass the document service: raw
|
|
1007
|
+
`strapi.db.query`, knex or SQL, imports that write the database directly. This
|
|
1008
|
+
is content activity for editors, not a security audit log.
|
|
1009
|
+
|
|
1010
|
+
**Failure policy.** A failed capture never blocks a save or a publish: the
|
|
1011
|
+
change goes through, the error is logged and the event is marked "Not
|
|
1012
|
+
recorded". A failed capture fails a delete: the delete runs in one transaction
|
|
1013
|
+
with its events, so nothing is deleted without its last version (a bulk delete
|
|
1014
|
+
is rolled back whole). `BLOCKSCENE_DISABLED=true` or the module switch off stop
|
|
1015
|
+
all capture. The plugin never records its own content types.
|
|
1016
|
+
|
|
1017
|
+
**In the edit view.** The Blockscene side panel gets a History section (saved
|
|
1018
|
+
documents of covered types, also on types without a Dynamic Zone): the
|
|
1019
|
+
versions of the document in the current locale, newest first. Selecting one
|
|
1020
|
+
shows a block-level diff against the form (relations are not compared) and
|
|
1021
|
+
"Load this version", which puts the version into the form the way undo does:
|
|
1022
|
+
nothing is written until the editor saves, so validation, permissions and
|
|
1023
|
+
lifecycles apply as for any edit, and undo reverts the load. Components and
|
|
1024
|
+
blocks come back as new rows; relations become the connect/disconnect change
|
|
1025
|
+
from what the document has now; fields the editor may not update, fields the
|
|
1026
|
+
schema no longer has, blocks whose component is no longer allowed, and media or
|
|
1027
|
+
related entries that no longer exist are left out and listed.
|
|
1028
|
+
|
|
1029
|
+
**Visual editor history (Phase 1).** The preview toolbar's `versions` control
|
|
1030
|
+
opens a drawer on the right. It is separate from `history` (Undo/Redo). If
|
|
1031
|
+
you already configured `previewToolbar`, add `versions` to that list or enable
|
|
1032
|
+
it in Settings. Versions are grouped by local calendar day, with local date
|
|
1033
|
+
and time, actor, action, and captured counts: total top-level Dynamic Zone rows
|
|
1034
|
+
(including group markers), added, removed and changed against the preceding
|
|
1035
|
+
snapshot in that locale. Older or unrecorded events show counts as unavailable;
|
|
1036
|
+
they are not retroactively counted as zero. The actor filter includes only
|
|
1037
|
+
people/tokens recorded for this document and locale. Lists use server pagination
|
|
1038
|
+
(25 per page) and mark events whose snapshots are no longer available.
|
|
1039
|
+
|
|
1040
|
+
Selecting an event hydrates its retained snapshot into a separate, read-only
|
|
1041
|
+
preview. It never calls the form's `setValues`, saves, publishes or restores
|
|
1042
|
+
anything. The banner identifies the version and returns to the current draft,
|
|
1043
|
+
including unsaved changes. Fast selections, returning while a load is pending,
|
|
1044
|
+
and document/locale/permission changes invalidate old responses. The pre-existing
|
|
1045
|
+
side-panel "Load this version" action remains available; restoration and visual
|
|
1046
|
+
diff enhancements are separate phases.
|
|
1047
|
+
|
|
1048
|
+
The parent rejects every mutation, selection, focus and hover message while
|
|
1049
|
+
viewing a version. Each version changes the iframe channel, so delayed messages
|
|
1050
|
+
cannot target the current draft after returning. The `update-page` message adds
|
|
1051
|
+
`readOnly: true`, sends no editable field/media descriptors and no clipboard.
|
|
1052
|
+
The reference bridge in `examples/page-preview` supports this flag, removes
|
|
1053
|
+
editing/insertion controls, and announces `capabilities: ['readOnly']` in `ready`.
|
|
1054
|
+
Custom bridges should do the same and refuse to queue or send edits when
|
|
1055
|
+
read-only. An older bridge can still render the snapshot, but the admin disables
|
|
1056
|
+
iframe interaction (including in-frame scrolling) and explains that it needs an
|
|
1057
|
+
update. Parent-side protection applies regardless of bridge support. The existing
|
|
1058
|
+
`hover` protocol highlights; it does not promise scroll-to-block behavior.
|
|
1059
|
+
|
|
1060
|
+
The drawer button also explains whether history is disabled/not configured for
|
|
1061
|
+
the type, the entry needs its first save, or `history.read` is missing. Permission
|
|
1062
|
+
changes require an administrator; this control cannot grant access.
|
|
1063
|
+
|
|
1064
|
+
**Permission.** `Blockscene: Read version history` (`history.read`), plus read
|
|
1065
|
+
access to the content type in the Content Manager. Snapshots hold removed and
|
|
1066
|
+
unpublished text, so the section is hidden without it. Admin API:
|
|
1067
|
+
`GET /blockscene/history/:uid/:documentId?locale=&page=1&pageSize=25&actor=`
|
|
1068
|
+
(no snapshots; `results`, `pagination`, scoped `actors` and resolved `locale`)
|
|
1069
|
+
and `GET /blockscene/history-events/:id` (one event, its snapshot and
|
|
1070
|
+
the media and relation targets that still exist).
|
|
1071
|
+
|
|
1072
|
+
**Retention.** Snapshots are kept `retentionDays` (90) and at most
|
|
1073
|
+
`maxSnapshots` (100) per document, newest first; events without a snapshot are
|
|
1074
|
+
kept `eventDays` (365, at least `retentionDays`). A cron job added by the plugin
|
|
1075
|
+
runs nightly at 03:00 server time, in batches of 500; it only empties snapshots
|
|
1076
|
+
and deletes events, never media files, and running it on several instances is
|
|
1077
|
+
harmless.
|
|
1078
|
+
These are configurable limits, not a guarantee that a version from a week ago
|
|
1079
|
+
still exists: frequent changes can exceed `maxSnapshots` earlier. The snapshot
|
|
1080
|
+
cap is per document across locales; event metadata may outlive its snapshot.
|
|
1081
|
+
Naming versions and protecting them from purge are not implemented in Phase 1.
|
|
1082
|
+
|
|
1083
|
+
```js
|
|
1084
|
+
// config/plugins.js
|
|
1085
|
+
blockscene: { config: { settings: { history: { enabled: true, contentTypes: ['api::page.page', 'api::post.post'],
|
|
1086
|
+
retentionDays: 90, maxSnapshots: 100, eventDays: 365, trashDays: 90 } } } }
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
Events live in the `blockscene_events` table (content type
|
|
1090
|
+
`plugin::blockscene.event`, hidden from the Content Manager and the Content-Type
|
|
1091
|
+
Builder). Removing the plugin, or installing a Blockscene version older than
|
|
1092
|
+
the one that added history, lets Strapi drop that table and every recorded
|
|
1093
|
+
version on the next start: back up `blockscene_events` first if you need it.
|
|
1094
|
+
|
|
1095
|
+
### Trash (Strapi 5)
|
|
1096
|
+
|
|
1097
|
+
Part of the version history module: on with it, for the same content types.
|
|
1098
|
+
Strapi has no trash in any plan; here a delete made through the document service
|
|
1099
|
+
(the admin's Delete, bulk delete and "Delete locale", the APIs, plugins) also
|
|
1100
|
+
writes one trash entry per document, in the same transaction as the delete.
|
|
1101
|
+
|
|
1102
|
+
**What an entry keeps.** Every deleted locale, draft and published rows (they
|
|
1103
|
+
can differ), as snapshots like the history's; the document's title (its Content
|
|
1104
|
+
Manager main field); who deleted it and when; and the links other documents and
|
|
1105
|
+
blocks had to it. Strapi removes those links with the document, and the
|
|
1106
|
+
document's own snapshot does not hold them when the relation is unidirectional
|
|
1107
|
+
(declared only on the other side, like a menu listing pages or a block linking
|
|
1108
|
+
to a page), so they are captured before the delete: owner, field, locale and the
|
|
1109
|
+
position in the owner's list. A delete without a locale removes the default
|
|
1110
|
+
locale only (Strapi's rule), `'*'` every locale; the entry has exactly what was
|
|
1111
|
+
deleted. If the capture fails, nothing is deleted.
|
|
1112
|
+
|
|
1113
|
+
**Restore.** From the Trash page, which first shows what the restore would do
|
|
1114
|
+
and writes nothing if something blocks it:
|
|
1115
|
+
|
|
1116
|
+
- Blocking: the content type is gone; a unique field (`unique: true` or a uid
|
|
1117
|
+
field, such as a slug) is now used by another document in that locale; the
|
|
1118
|
+
document has that locale again; a single type has a document again; none of
|
|
1119
|
+
its locales is configured any more.
|
|
1120
|
+
- Reported, then restored anyway: a published version existed (see below);
|
|
1121
|
+
required fields are empty under the current schema (drafts may be, Strapi
|
|
1122
|
+
checks them on publish); fields the schema no longer has, blocks whose
|
|
1123
|
+
component the zone no longer allows, media files and related documents that no
|
|
1124
|
+
longer exist (left out); locales no longer configured (skipped); links whose
|
|
1125
|
+
owner is gone or now points elsewhere.
|
|
1126
|
+
|
|
1127
|
+
The document comes back as a **draft with its draft content**: the draft is the
|
|
1128
|
+
latest work, and publishing is the editor's decision (the site may have moved
|
|
1129
|
+
on). The published snapshot stays in the entry and in the preview. Every locale
|
|
1130
|
+
is restored under one documentId, the default locale first, through the
|
|
1131
|
+
document service (validation and the project's lifecycles run). A document that
|
|
1132
|
+
is gone gets a new documentId, since Strapi's create does not take one; a locale
|
|
1133
|
+
deleted from a document that still exists goes back into that document. The
|
|
1134
|
+
captured links are put back where they were (same position in the owner's
|
|
1135
|
+
list) when the owner still exists; links from published versions come back
|
|
1136
|
+
when those owners are published again after this document is. A restore is one
|
|
1137
|
+
transaction and is idempotent: the entry is claimed first, so a second restore
|
|
1138
|
+
(a double click, another editor, another instance) returns the first one's
|
|
1139
|
+
document. The history gets a `restore` event per locale.
|
|
1140
|
+
|
|
1141
|
+
**Delete forever** removes one entry (recorded as a `purge` event in the
|
|
1142
|
+
activity). The nightly job deletes entries past their expiry, restored ones too,
|
|
1143
|
+
in batches; neither ever deletes media files.
|
|
1144
|
+
|
|
1145
|
+
**Retention.** `trashDays` (90, 1 to 3650) in the same settings; each entry's
|
|
1146
|
+
expiry is fixed when the document is deleted, so a later change applies to later
|
|
1147
|
+
deletes.
|
|
1148
|
+
|
|
1149
|
+
```js
|
|
1150
|
+
blockscene: { config: { settings: { history: { enabled: true, trashDays: 30 } } } }
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
Entries live in `blockscene_trash` (`plugin::blockscene.trash`, hidden from the
|
|
1154
|
+
Content Manager and the Content-Type Builder); the same backup note as for
|
|
1155
|
+
`blockscene_events` applies.
|
|
1156
|
+
|
|
1157
|
+
### Activity and Trash pages (Strapi 5)
|
|
1158
|
+
|
|
1159
|
+
A main menu link, "Activity and trash", opens one page with a tab each (the
|
|
1160
|
+
pages are tools for editors, not configuration, so they are not under
|
|
1161
|
+
Settings). **Activity** lists every recorded event, newest first, 25 per page,
|
|
1162
|
+
filtered by person, content type, action, date range and title (documents whose
|
|
1163
|
+
main field contains the text now, or whose trash entry's title does); each row
|
|
1164
|
+
links to the document's edit view while that locale exists and shows the
|
|
1165
|
+
summary. **Trash** lists the entries (title, type, locales, who, when, when it
|
|
1166
|
+
is deleted for good), filtered by content type, with restored entries on a
|
|
1167
|
+
switch; Preview shows the fields and blocks of each deleted locale, read-only;
|
|
1168
|
+
Restore shows the pre-check report and asks to confirm; Delete forever asks
|
|
1169
|
+
first.
|
|
1170
|
+
|
|
1171
|
+
**Permissions.** `Read content activity` (`activity.read`), `Read the trash`
|
|
1172
|
+
(`trash.read`), `Restore from the trash` (`trash.restore`), `Delete from the
|
|
1173
|
+
trash forever` (`trash.purge`), none granted to non-super-admin roles by
|
|
1174
|
+
default. On top of them, every list shows only content types the user may read
|
|
1175
|
+
in the Content Manager (an entry of another type answers 404), restoring needs
|
|
1176
|
+
create there and deleting forever needs delete. Admin API: `GET /blockscene/activity`
|
|
1177
|
+
(`actor`, `contentType`, `action`, `from`, `to` as ISO dates, `q`, `page`,
|
|
1178
|
+
`pageSize` up to 100), `GET /blockscene/trash` (`contentType`, `status`
|
|
1179
|
+
trashed or restored, `page`, `pageSize`), `GET /blockscene/trash/:id` (the
|
|
1180
|
+
preview), `GET /blockscene/trash/:id/check` (the pre-check),
|
|
1181
|
+
`POST /blockscene/trash/:id/restore` (409 with the report when blocked) and
|
|
1182
|
+
`DELETE /blockscene/trash/:id`. Lists never load snapshots.
|
|
1183
|
+
|
|
1184
|
+
**Coverage boundary**, the same as the history's: the document service only.
|
|
1185
|
+
A raw `strapi.db.query` delete, SQL or a database restore leaves no trash
|
|
1186
|
+
entry; deleting a content type (Content-Type Builder) drops its data without
|
|
1187
|
+
one. The trash is a safety net for editors, not a backup: keep database backups.
|
|
1188
|
+
|
|
231
1189
|
## Languages
|
|
232
1190
|
|
|
233
1191
|
All plugin chrome (gallery, settings, page preview, editor dialogs, diagnostics)
|
|
@@ -241,15 +1199,15 @@ component display names are never translated by the plugin.
|
|
|
241
1199
|
|
|
242
1200
|
## Turning the editor enhancements off
|
|
243
1201
|
|
|
244
|
-
- **From the
|
|
245
|
-
|
|
1202
|
+
- **From the settings page**: switch "Editor enhancements enabled" off. The
|
|
1203
|
+
gallery, the zone bars and the initial-state logic disappear and the native picker and
|
|
246
1204
|
accordions remain. Content, config, media and unsaved edits are preserved; the
|
|
247
1205
|
form is not reloaded. The settings page stays available to turn it back on.
|
|
248
|
-
- **Emergency, outside the
|
|
1206
|
+
- **Emergency, outside the admin**: start the server with
|
|
249
1207
|
`BLOCKSCENE_DISABLED=true` (or set `config.disabled: true`). It is read at
|
|
250
1208
|
boot and reported by the catalog, wins over the saved settings and cannot be
|
|
251
1209
|
undone from the client. Changing it requires a restart.
|
|
252
|
-
- **If the catalog request fails**, the
|
|
1210
|
+
- **If the catalog request fails**, the plugin renders nothing and the native
|
|
253
1211
|
editor is used, so a plugin outage does not block editing.
|
|
254
1212
|
- **Completely**: set `'blockscene': { enabled: false }` in `config/plugins.js`
|
|
255
1213
|
and rebuild/restart. This is the only way out of an import/build failure; a
|
|
@@ -292,10 +1250,11 @@ the configured static base.
|
|
|
292
1250
|
|
|
293
1251
|
## Guarantees and limits
|
|
294
1252
|
|
|
295
|
-
Tested versions: Strapi 4.26.1 (Node 20) and 5.52.1
|
|
296
|
-
labs of this repository, and 5.23.5 and 5.31.0 in two
|
|
297
|
-
peer ranges (`>=5.
|
|
298
|
-
overrides, not a promise for every minor in between.
|
|
1253
|
+
Tested versions: Strapi 4.11.0 and 4.26.1 (Node 20), 5.0.0 and 5.52.1
|
|
1254
|
+
(Node 22) in the local labs of this repository, and 5.23.5 and 5.31.0 in two
|
|
1255
|
+
real site projects. The peer ranges (`>=5.0.0 <6`, `>=4.11.0 <5`) express what
|
|
1256
|
+
installs without overrides, not a promise for every minor in between. Strapi
|
|
1257
|
+
4.0–4.10 is out of reach: those admins run React 17 and lack `useFetchClient`.
|
|
299
1258
|
|
|
300
1259
|
|
|
301
1260
|
- Native component payloads and ordinary Strapi save/publish flow.
|
|
@@ -306,14 +1265,17 @@ overrides, not a promise for every minor in between.
|
|
|
306
1265
|
- Authenticated admin catalog endpoint exposes only supported metadata, resolved
|
|
307
1266
|
override URLs, palette and editor preferences; never the whole config.
|
|
308
1267
|
- Top-level Dynamic Zones only. Conditional fields are omitted conservatively.
|
|
309
|
-
- "
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
1268
|
+
- "Expand all" / "Collapse all" drive the native accordions without touching the
|
|
1269
|
+
whole list at once: expanding opens the rows in and near the visible area, a
|
|
1270
|
+
few per frame, and the others as they approach while scrolling (until Collapse
|
|
1271
|
+
all or leaving the document); collapsing closes the visible rows at once and
|
|
1272
|
+
the rest in idle time. With lazy editors, on a 34-block page with CKEditor
|
|
1273
|
+
fields (Strapi 5.31) the longest pause went from about 2.9 s to 0.14 s on
|
|
1274
|
+
open all and from 1.6 s to 0.14 s on close all.
|
|
314
1275
|
- No screenshot server, no hourly job, no in-editor rendered preview.
|
|
315
1276
|
- Removing the plugin leaves native content intact; remove its config entry and
|
|
316
|
-
rebuild. No plugin data migration is needed.
|
|
1277
|
+
rebuild. No plugin data migration is needed. The "hide on the site" column
|
|
1278
|
+
(see Row actions) stays in the component tables until you drop it.
|
|
317
1279
|
|
|
318
1280
|
Configuration edits require a server restart. Backend URL/prefix deployments
|
|
319
1281
|
should set `previewBaseUrl` explicitly. HTTP(S) image hosts must also be allowed
|
|
@@ -324,13 +1286,14 @@ uploads share an origin.
|
|
|
324
1286
|
## Development
|
|
325
1287
|
|
|
326
1288
|
- `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).
|
|
1289
|
+
- `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).
|
|
328
1290
|
- `admin/translations/`: one flat catalogue per admin locale (English is the source of truth).
|
|
329
1291
|
- `scripts/capture-previews.mjs`: local thumbnail capture.
|
|
330
1292
|
- `packages/strapi4`, `packages/strapi5`: independently installable distributions.
|
|
331
1293
|
- `tests/`: runnable regression checks.
|
|
332
1294
|
- [Local integration tests](docs/LOCAL-TESTING.md).
|
|
333
1295
|
- [Release checklist](docs/RELEASE.md).
|
|
1296
|
+
- [Prioritized roadmap](docs/ROADMAP.md).
|
|
334
1297
|
|
|
335
1298
|
This plugin does not require Image Optimization; both can be installed together.
|
|
336
1299
|
|