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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,8 @@ module.exports = {
33
33
  'blocks.hero': {
34
34
  label: 'Hero banner',
35
35
  description: 'Introduce a page with a prominent title and image.',
36
- category: 'Editorial',
36
+ typology: 'hero', // optional; guessed from the name otherwise
37
+ tags: ['Editorial'],
37
38
  keywords: 'heading introduction',
38
39
  image: '/block-previews/hero.png',
39
40
  },
@@ -44,19 +45,75 @@ module.exports = {
44
45
  ```
45
46
 
46
47
  Rebuild/restart the admin. Open a content type with a Dynamic Zone. The
47
- editor panel provides one gallery button per editable, non-full zone plus
48
- "Open all blocks" / "Close all blocks" for zones that already have blocks.
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)").
49
51
 
50
52
  ## Gallery
51
53
 
52
- The modal has one header row: block count (`34 blocks`, or `3 of 34` while
53
- filtering), search, a category filter (only when the zone spans more than one
54
- category, with counts), a "Fields" switch that shows each block's attribute
55
- list on its card, and a columns slider (1 to 5, remembered per browser). Blocks
56
- are grouped by category, system-like categories (`admin`) last; search and the
57
- category filter combine, and the empty state offers a reset. Categories come
58
- from `config.components[uid].category`, else the component's `category` in its
59
- schema. The modal is 80vw wide.
54
+ The modal is a block browser (80vw wide):
55
+
56
+ - **Sidebar** (collapsible to icons; remembered per browser): All, Recently
57
+ used and Starred with counts, then the typologies present in the zone.
58
+ - **Top bar**: count (`34 blocks`, or `3 of 34` while filtering), search,
59
+ filter menus Tags, Media and Content (checkboxes; several values in one menu
60
+ match any of them, different menus combine), a "Fields" switch that shows
61
+ each block's attribute list on its card, and a columns slider (1 to 5,
62
+ remembered per browser). Active values show as removable chips with "Clear
63
+ all"; the empty state offers a reset.
64
+ - **Cards**, grouped by typology: facet badges (IMAGE, VIDEO, GALLERY, RICH
65
+ TEXT, LIST, DYNAMIC, FORM), a star and a hover "+" that inserts at once.
66
+ - A click **magnifies** the block: the card grows into a large panel over the
67
+ grid (sidebar and top bar stay usable, the grid dims underneath) and shrinks
68
+ back into its card on close (close button, Esc or a click on the dimmed
69
+ grid). The animation is transform/opacity only, about 250 ms, and is skipped
70
+ under `prefers-reduced-motion: reduce`. The panel shows a large live preview
71
+ with Fit / Mobile / Tablet / Desktop widths (scaled to fit, as in the page
72
+ preview) and a compact strip: label, uid, description, badges, fields (on
73
+ demand), star and Insert. Double click, "+" or Enter on a focused card
74
+ inserts without it.
75
+
76
+ The magnified preview shows the card's thumbnail at once and fades the live
77
+ page in over it once it has loaded. Live source, first match wins:
78
+
79
+ 1. **Block preview URL** (`editor.blockPreviewUrl`, Settings, both versions):
80
+ a plain page per block in a plain iframe, no bridge. Placeholders, URL
81
+ encoded: `{uid}`, `{name}` (after the dot), `{category}` (before the dot),
82
+ `{variant}` (`default`) and `{locale}` (the entry's locale, empty when the
83
+ type is not localized). Example:
84
+ `http://localhost:3026/{locale}/block-preview/{name}/{variant}`. Such a
85
+ fixtures route is usually dev-only: leave the option empty on production.
86
+ 2. **Page preview route** (Strapi 5: the preview route base URL, else the
87
+ native Preview origin + `/block-preview/page`): the same bridge as the
88
+ whole-page preview, sent one block made from the schema defaults, mode
89
+ `preview`. Read-only: every message except `ready` is ignored.
90
+ 3. Otherwise the image only.
91
+
92
+ If the live page neither loads nor says `ready` within 8 s, the image stays
93
+ with a short note. The iframe uses the page preview's sandbox
94
+ (`allow-scripts allow-same-origin`) and `referrerpolicy="no-referrer"`.
95
+
96
+ Stars and recently used blocks are stored per admin user on the server
97
+ (`GET`/`PUT /blockscene/me/prefs`, `{ starred, recent }`, at most 200 and 20
98
+ existing component uids).
99
+
100
+ ### Taxonomy
101
+
102
+ Facets are read from each component schema and the components it nests (one
103
+ level): media fields (`image`, `video` by allowed types, `gallery` when
104
+ multiple), `richtext` (rich text, blocks or a CKEditor custom field), `list`
105
+ (repeatable component), `dynamic` (a relation, or a uid with query, archive or
106
+ related) and `form` (uid or display name with form or contact).
107
+
108
+ The typology is one of hero, text, media, listing, cards, cta, form and
109
+ layout, guessed from the component name and display name (hero, banner,
110
+ cover; text, rich, quote, title; image, media, video, gallery, carousel; list,
111
+ query, archive, related, posts, projects; card; cta, button, link; form,
112
+ contact; wrapper, column, grid, divider, spacer, section; else text). Override
113
+ it, and add up to 10 tags (1 to 24 characters), in Settings, Blockscene, or in
114
+ the plugin config: `components[uid].typology` and `components[uid].tags`. A
115
+ Settings value wins over the config. The former `category` config key still
116
+ works: it becomes the block's tag when no tags are set.
60
117
 
61
118
  The native "Add a component to <zone>" button of an editable zone opens this
62
119
  gallery as well (the click is intercepted in the capture phase; Strapi's
@@ -66,6 +123,64 @@ label ending with the raw zone name, which Strapi's message carries in every
66
123
  locale; a zone whose name ends another zone's name (`blocks` and `sub blocks`)
67
124
  is the documented limit.
68
125
 
126
+ ## Row thumbnails
127
+
128
+ Each added block shows its thumbnail at the left of the native accordion
129
+ header, and clicking it opens the image full size in a modal instead of
130
+ opening or closing the row. The sources are the same as the gallery cards,
131
+ minus the wireframe: a block with no image shows nothing, so nothing new is
132
+ invented for blocks that were never captured. The thumbnail is inserted into
133
+ Strapi's own header through the DOM (no Content Manager patch) and is
134
+ restored when Strapi re-renders the list. Turn it off with the editor option
135
+ `showRowThumbnails: false`.
136
+
137
+ ## Row actions
138
+
139
+ Each block header gets a few icons right before Strapi's own delete button
140
+ (inserted through the DOM, like the thumbnails; read-only rows get none):
141
+
142
+ - **Hide on the site** (eye). The block stays in the document and in the
143
+ admin, dimmed with a "Hidden" badge (also in the page preview, which receives
144
+ `hidden: true` for it). What the content API does is the editor option
145
+ `hiddenBlocks`: `strip` (default) removes hidden rows from Dynamic Zones in
146
+ content-API reads (REST and GraphQL, including zones of populated relations
147
+ and components; admin and Content Manager reads always return every row),
148
+ `flag` sends them with the attribute so the frontend decides, `off` removes
149
+ the eye and strips nothing. On a configured group OPEN the eye hides the
150
+ whole group.
151
+ - **Duplicate**. A deep copy right below: ids removed from the row and its
152
+ nested components, new row key, media kept, relations kept (Strapi 5 reads
153
+ the source row's relations from the server and adds its unsaved changes).
154
+ 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
157
+ 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
163
+ within the same install. Option `clipboard`.
164
+ - **Confirm delete**. The native delete asks "Delete block …?" first;
165
+ confirming runs Strapi's own removal. On a group marker the dialog says only
166
+ the marker is removed. Option `confirmDelete`.
167
+
168
+ Everything goes through the form: nothing is saved until Save, and undo/redo
169
+ (Strapi 5) covers every action. Strapi 4 supports the same actions; relations
170
+ are copied as the form holds them there (the loaded relations of an opened
171
+ block).
172
+
173
+ **The hidden attribute is a database column.** At register time the plugin adds
174
+ 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
179
+ `hiddenAttribute: 'myName'`, or set `hiddenAttribute: false` to add nothing (no
180
+ eye, nothing stripped). A component that already has an attribute of that name
181
+ with another type is skipped with a warning. Removing the plugin (or setting
182
+ `false`) leaves the column in place with its data; drop it yourself if you want.
183
+
69
184
  ## Thumbnail priority
70
185
 
71
186
  Each card tries these sources in order and moves on when one fails to load.
@@ -99,9 +214,57 @@ needed for the gallery to pick them up. Reading requires the
99
214
  - Wireframe palette (background, surfaces, text, accent) with a live preview
100
215
  and "Restore default colors". Colors must be `#RRGGBB`.
101
216
  - Component list with the effective source, choose/replace image from the
102
- Media Library, "Use automatic image" and the wireframe template.
217
+ Media Library, "Use automatic image", the wireframe template, the gallery
218
+ typology ("Automatic" shows the guess) and tags (comma separated).
103
219
  - Editor preferences: enhancements on/off, visibility of each collective
104
220
  button, initial accordion state and the initial block mode.
221
+ - Content types: every project type with a Dynamic Zone, each with on/off
222
+ (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).
225
+ - "Restore project defaults" (or "Reset to defaults" when the project has no
226
+ code defaults), after a confirmation, deletes what was saved here
227
+ (`DELETE /blockscene/settings`, same permission as saving).
228
+
229
+ ### Project defaults from code
230
+
231
+ A project can ship its baseline in the plugin config, with the same shape as
232
+ the stored settings (every key optional):
233
+
234
+ ```js
235
+ // config/plugins.js
236
+ module.exports = {
237
+ blockscene: { config: { settings: require('./blockscene.json') } },
238
+ }
239
+ // config/blockscene.json
240
+ {
241
+ "palette": { "accent": "#AA3300" },
242
+ "editor": { "previewMode": "split", "initialState": "open" },
243
+ "components": { "blocks.faq": { "template": "faq", "typology": "text" } },
244
+ "contentTypes": {
245
+ "api::page.page": {
246
+ "sidebarPosition": "right",
247
+ "sidebar": [{ "label": "SEO", "icon": "seo", "open": "drawer", "fields": ["seo"] }]
248
+ }
249
+ }
250
+ }
251
+ ```
252
+
253
+ The effective settings are the built-in defaults, then the code `settings`,
254
+ then what was saved on the page:
255
+
256
+ - `palette` and `editor`: a saved key wins over the code key by key.
257
+ - `components` and `contentTypes`: a saved entry replaces the code entry for
258
+ that uid as a whole (a saved `api::page.page` entry replaces its code
259
+ sidebar too); other uids keep their code values.
260
+
261
+ Saving stores only what differs from the code defaults, so later changes to the
262
+ code reach every key nobody overrode. A document saved before the project had
263
+ code defaults holds every palette and editor key, and those keep winning until
264
+ "Restore project defaults". The code settings are checked at boot with the same
265
+ rules as `PUT`; if they are invalid a warning names the problem and they are
266
+ ignored (boot continues). `GET /blockscene/settings` returns them as
267
+ `projectDefaults` (null when absent or invalid).
105
268
 
106
269
  ### Editor preferences
107
270
 
@@ -110,14 +273,35 @@ needed for the gallery to pick them up. Reading requires the
110
273
  | Editor enhancements enabled | on/off | on |
111
274
  | Show "Open all blocks" | yes/no | yes |
112
275
  | Show "Close all blocks" | yes/no | yes |
276
+ | Show block thumbnails in the rows (`showRowThumbnails`) | yes/no | yes |
113
277
  | Initial accordion state | all closed / all open / remember | all closed |
278
+ | Load rich-text editors on demand (`lazyEditors`, Strapi 5) | yes/no | yes |
279
+ | Custom fields loaded on demand (`lazyFields`, Strapi 5) | up to 20 uids `plugin::x.y` / `global::x` | `["plugin::ckeditor5.CKEditor"]` |
114
280
  | Preview route base URL (Strapi 5) | full URL or empty | empty: native Preview origin |
281
+ | Block preview URL | http(s) URL with placeholders, up to 500 characters, or empty | empty: page preview route, else image |
115
282
  | Initial preview mode (Strapi 5) | form / side by side / preview | form |
283
+ | Confirm before deleting a block (`confirmDelete`) | yes/no | yes |
284
+ | Duplicate on each block (`duplicate`) | yes/no | yes |
285
+ | Copy and Paste (`clipboard`) | yes/no | yes |
286
+ | Blocks hidden on the site (`hiddenBlocks`) | `strip` / `flag` / `off` | `strip` |
287
+
288
+ Every edit view opens in its content type's mode, else the initial preview
289
+ mode. Editors can switch while they edit; the switch is not remembered.
116
290
 
117
291
  The initial state is applied once per document/locale after the blocks
118
292
  render; edits, reorders and newly inserted blocks are not re-applied. Hiding a
119
293
  button only hides it; native accordion headers keep working.
120
294
 
295
+ Lazy editors: a custom field listed in `lazyFields` first shows a read-only
296
+ preview of its HTML (same label, hint and error; scripts, frames, styles, event
297
+ handlers and `javascript:` URLs removed) and mounts the real editor on click,
298
+ Enter/Space or keyboard focus, then puts the caret in it. Once mounted it stays
299
+ for that view; closing and reopening the block shows the preview again.
300
+ Disabled fields only show the preview. The wrapper is installed on every
301
+ registered custom field at boot and passes the others straight through; until
302
+ the catalog loads, and when the plugin or the content type is off, every field
303
+ is native.
304
+
121
305
  "Remember" stores only the last explicit open all / close all click, as
122
306
  `open`/`closed` in the browser's localStorage, keyed by admin URL, user id,
123
307
  content type and zone. Another user in the same browser does not inherit it.
@@ -137,11 +321,62 @@ your frontend serves; the page renders them with its own components and styles
137
321
  and can ask the admin to open a block, edit a field or pick media. Nothing is
138
322
  saved or published by the preview.
139
323
 
324
+ Side by side, hovering a zone row in the form (open or closed) outlines its
325
+ block in the page, and hovering a block in the page outlines its form row in the
326
+ theme's primary color; hovering never scrolls. When that row is outside the
327
+ visible form, an arrow at the top or bottom edge of the form scrolls to it. The
328
+ divider between form and page drags to resize, takes the arrow keys, Home and
329
+ End, and a double click resets it to half.
330
+
331
+ ### Visual editor sidebar
332
+
333
+ Per content type, a bar of buttons on the left, right or bottom edge of the
334
+ visual editor. Each button (label, optional icon) opens some of the document's
335
+ own fields in a modal or a drawer: the native form itself, with every other
336
+ field hidden, so components, repeatable components, media, relations and custom
337
+ fields all keep working. Done, Esc or the backdrop put the form back.
338
+
339
+ ```js
340
+ // Settings, Blockscene, Content types (or PUT /blockscene/settings, or the code `settings`):
341
+ contentTypes: {
342
+ 'api::page.page': {
343
+ sidebarPosition: 'left', // left | right | bottom
344
+ sidebar: [
345
+ { label: 'Title', icon: 'text', open: 'modal', fields: ['title'] },
346
+ { label: 'SEO', icon: 'seo', open: 'drawer', fields: ['pageSeo'] },
347
+ ],
348
+ },
349
+ }
350
+ ```
351
+
352
+ Icons: text, tag, seo, settings, image, link, palette, list, globe, info (or
353
+ none). Fields are the type's own top-level attributes; an item naming a field
354
+ removed from the schema is dropped on read. Up to 12 items.
355
+
356
+ The Settings page edits it under each enabled content type: position, then per
357
+ item the label, icon, drawer or modal, the fields (checkboxes), move up/down and
358
+ remove. An item without a label or without fields is flagged and blocks Save.
359
+ Removing every item removes the override.
360
+
361
+ The pane toolbar sets the page width: Fit (the pane), Mobile (390 px), Tablet
362
+ (834 px) or Desktop (1440 px). A device renders at its own width and scales down
363
+ when the pane is narrower, so the desktop layout fits side by side; switching
364
+ never reloads the page. The choice is remembered per browser.
365
+
140
366
  Minimal configuration:
141
367
 
142
368
  1. Serve a page that implements the bridge. `examples/page-preview/index.html`
143
369
  is a framework-free reference: it answers `ready`/`ping`, renders
144
370
  `update-page`, and sends `select`, `focus`, `edit`, `media`, `media-remove`.
371
+ Hover sync is optional: the page sends `{ type: 'hover', key }` (a block
372
+ key, or `null` off blocks) when it changes, and marks the block of the
373
+ admin's `{ type: 'hover', key }` without scrolling. A page that ignores it
374
+ keeps working.
375
+ Row actions: a block hidden on the site arrives with `hidden: true` (the
376
+ example dims it), and `update-page` carries `clipboard` (the number of copied
377
+ blocks, 0 when none or not editable); while it is positive the page may show
378
+ a Paste button in its seams that sends `{ type: 'paste', after }` (a block
379
+ key, or `null` for the start). Both are optional.
145
380
  Keep the origin check (`admin` query parameter or your own constant).
146
381
  2. Allow the admin origin to embed it (`Content-Security-Policy: frame-ancestors`)
147
382
  and allow the page origin in the Strapi admin CSP (`frame-src`).
@@ -161,14 +396,34 @@ update permission on it. Pending dialogs are dropped when the document or
161
396
  locale changes.
162
397
 
163
398
  Editing from the page: plain-text areas the page explicitly maps
164
- (`data-block-field` + `contenteditable="plaintext-only"`) send `edit`; any other
165
- editable field opens an admin modal with the field's own native input (custom
166
- fields such as CKEditor keep their options; string/text use the admin input).
167
- Apply updates the form and the preview; Cancel discards only the modal. Media
399
+ (`data-block-field` + `contenteditable="plaintext-only"`) send `edit`. In the
400
+ visual editor, a click on any other field or on the block opens the whole block
401
+ over the page: its own native form (every field type, CKEditor, media, nested
402
+ and repeatable components), with the clicked field focused and outlined. The
403
+ page updates while you type; Done, Esc or the backdrop put the block back. Side
404
+ by side focuses the field in the form on the left instead. Media
168
405
  fields open the native Media Library. Everything is validated against the schema
169
406
  and the zone's edit permission; a published page never receives unsaved values
170
407
  by itself. Strapi 4 does not have this panel yet (see `docs/BACKLOG.md`).
171
408
 
409
+ ### Undo and redo
410
+
411
+ The Blockscene panel and the pane toolbar have Undo and Redo buttons (Strapi 5).
412
+ They step through the whole edit view: blocks inserted, removed or moved from the
413
+ gallery, the page, groups or the native actions, and any field edit. Changes
414
+ 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
416
+ the focus is outside a text field, select or rich text editor (those keep their
417
+ own undo).
418
+
419
+ Limits: the history lives in the open edit view and starts over when the
420
+ document, the locale or its loaded values change, so a Save clears it. Shortcuts
421
+ pressed inside the preview page are not seen (it is another origin); use the
422
+ buttons there. A custom field that reads its value only once on mount keeps
423
+ showing the old text after an undo until the view reloads; the CKEditor 5
424
+ custom field (@_sh/strapi-plugin-ckeditor) follows the restored value (checked
425
+ on Strapi 5.31).
426
+
172
427
  ## Layout groups (optional)
173
428
 
174
429
  Some sites keep "wrappers" in the flat Dynamic Zone: an OPEN component, the
@@ -192,7 +447,8 @@ pairs naming unknown components are dropped. Several OPENs may share one CLOSE.
192
447
  With a valid map:
193
448
 
194
449
  - The gallery (form mode) and the page preview insert OPEN and its CLOSE in the
195
- same unsaved change: an empty group, never a lone marker. When the CLOSE is
450
+ same unsaved change: an empty group, never a lone marker. A CLOSE is not
451
+ offered on its own in the gallery or the preview picker. When the CLOSE is
196
452
  not allowed in that zone, or the zone has no room for two rows, nothing is
197
453
  inserted and a notice explains why (two form dispatches in one React batch;
198
454
  no partial state is rendered or saved). Children are added
@@ -307,14 +563,17 @@ installs without overrides, not a promise for every minor in between. Strapi
307
563
  - Authenticated admin catalog endpoint exposes only supported metadata, resolved
308
564
  override URLs, palette and editor preferences; never the whole config.
309
565
  - Top-level Dynamic Zones only. Conditional fields are omitted conservatively.
310
- - "Open all" / "Close all" drive the native accordions: opening is batched into
311
- one render; closing yields a frame per block so the admin stays responsive.
312
- On a page with about 34 blocks whose fields include CKEditor, closing still
313
- takes a few seconds with pauses up to about 1.7 s: that is the editors being
314
- torn down by the host, not the plugin's loop.
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
570
+ the rest in idle time. With lazy editors, on a 34-block page with CKEditor
571
+ fields (Strapi 5.31) the longest pause went from about 2.9 s to 0.14 s on
572
+ open all and from 1.6 s to 0.14 s on close all.
315
573
  - No screenshot server, no hourly job, no in-editor rendered preview.
316
574
  - Removing the plugin leaves native content intact; remove its config entry and
317
- rebuild. No plugin data migration is needed.
575
+ rebuild. No plugin data migration is needed. The "hide on the site" column
576
+ (see Row actions) stays in the component tables until you drop it.
318
577
 
319
578
  Configuration edits require a server restart. Backend URL/prefix deployments
320
579
  should set `previewBaseUrl` explicitly. HTTP(S) image hosts must also be allowed