Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions apps/aurora/app/config/server/content-migrations.server.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it } from 'vitest';
import { PLONE_BLOCK_TYPE } from '@plone/helpers';
import config from '@plone/registry';
import { SOMERSAULT_KEY } from '@plone/plate/constants';
import type { Content, ContentBase } from '@plone/types';
Expand Down Expand Up @@ -65,11 +66,25 @@ describe('content migrations', () => {
});
});

it('moves native blocks into the somersault field as unknown nodes', () => {
it('moves native blocks into the somersault field as ploneBlock nodes', () => {
config.blocks = {
blocksConfig: {
listing: {},
image: {},
schemaWidth: {
blockSchema: {
title: 'Schema width block',
fieldsets: [],
required: [],
properties: {
blockWidth: {
widget: 'width',
default: 'full',
styleField: true,
},
},
},
},
},
} as typeof config.blocks;
installMigrations();
Expand All @@ -92,13 +107,16 @@ describe('content migrations', () => {
url: '/image',
alt: 'Example image',
},
schemaWidth: {
'@type': 'schemaWidth',
},
custom: {
'@type': 'custom-unregistered',
foo: 'bar',
},
},
blocks_layout: {
items: ['titleBlock', 'listing', 'image', 'custom'],
items: ['titleBlock', 'listing', 'image', 'schemaWidth', 'custom'],
},
};

Expand All @@ -114,19 +132,27 @@ describe('content migrations', () => {
},
{
'@type': 'listing',
blockWidth: 'default',
children: [{ text: '' }],
querystring: {
criteria: [],
},
type: 'unknown',
type: PLONE_BLOCK_TYPE,
},
{
'@type': 'image',
alt: 'Example image',
blockWidth: 'default',
children: [{ text: '' }],
type: 'unknown',
type: PLONE_BLOCK_TYPE,
url: '/image',
},
{
'@type': 'schemaWidth',
blockWidth: 'full',
children: [{ text: '' }],
type: PLONE_BLOCK_TYPE,
},
],
});
});
Expand Down
29 changes: 28 additions & 1 deletion apps/aurora/app/config/server/migrations.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import config from '@plone/registry';
import {
getStyleFieldsFromBlockSchema,
PLONE_BLOCK_TYPE,
} from '@plone/helpers';
import type { BlockConfigBase, BlocksFormData } from '@plone/types';
import {
migrateLegacyBoldInValue,
migrateLegacyBlockWidthsInValue,
Expand All @@ -23,6 +28,27 @@ const isRegisteredNativeBlock = (block: Record<string, unknown>) => {
return Boolean(blocksConfig?.[blockType]);
};

const DEFAULT_BLOCK_WIDTH = 'default';

const getMigratedPloneBlockWidth = (block: Record<string, unknown>) => {
const blockType = block['@type'];

if (typeof blockType !== 'string') {
return DEFAULT_BLOCK_WIDTH;
}

const blocksConfig = config.blocks?.blocksConfig as
| Record<string, BlockConfigBase>
| undefined;
const blockConfig = blocksConfig?.[blockType];
const styleFields = getStyleFieldsFromBlockSchema(
blockConfig,
block as BlocksFormData,
);

return styleFields.blockWidth?.defaultValue ?? DEFAULT_BLOCK_WIDTH;
};

export default function install() {
config.registerUtility({
name: 'somersaultBlockMigrationTitle',
Expand Down Expand Up @@ -57,7 +83,8 @@ export default function install() {
? [
{
...block,
type: 'unknown',
blockWidth: getMigratedPloneBlockWidth(block),
type: PLONE_BLOCK_TYPE,
children: [{ text: '' }],
},
]
Expand Down
2 changes: 1 addition & 1 deletion apps/aurora/news/+native-blocks-to-somersault.feature
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Registered Aurora native blocks are now migrated into the Somersault field as `type: 'unknown'` nodes so they can be rendered by the new editor pipeline later. @sneridagh
Registered Aurora native blocks are now migrated into the Somersault field as `type: 'ploneBlock'` nodes so they can be rendered by the new editor pipeline later. @sneridagh
117 changes: 117 additions & 0 deletions docs/development/block-anatomy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
myst:
html_meta:
"description": "Block anatomy contract for Plone Aurora"
"property=og:description": "Block anatomy contract for Plone Aurora"
"property=og:title": "Block anatomy"
"keywords": "Plone Aurora, blocks, Plate, block model, anatomy"
---

# Block anatomy

Plone Aurora exposes a shared block anatomy contract for both public rendering and Plate/Somersault rendering.

## Plone Aurora's block model

In the past, the Plone's block engines used different approaches, improving and iterating them over the years.
We identified several of these iterations, and defined what we called the Block Model v3.
It is the model behind Plone Aurora block anatomy contract, and from now on, it is simply referred to as the block model.
Its main goal is to keep view mode and edit mode structurally aligned so the same CSS can work in both places.
Instead of letting each block invent its own wrapper layout, the framework provides a standard two-level structure and leaves the block component focused on content and behavior.

The important ideas are:

- The outer container is responsible for full-width page placement, theme styling, and vertical spacing.
- The inner container controls content width, centering, and block-to-block spacing.
- Block categories drive spacing behavior between adjacent blocks, so spacing decisions stay consistent across the site.
- Blocks should stay simple and render their actual content directly, without adding extra layout wrappers unless they are genuinely needed.
- The model is opt-in, which keeps existing blocks compatible while allowing v3-capable blocks to adopt the shared structure.

In practice, that means the block model defines the structure around a block, while the block itself stays focused on the content it renders.

The outer block element receives:

```html
class="block block-<type> category-<category>"
data-block-type="<type>"
data-block-category="<category>"
```

For example, a teaser block in the teaser category renders as:

```html
<div
class="block block-teaser category-teaser"
data-block-type="teaser"
data-block-category="teaser"
>
<div class="block-inner-container">...</div>
</div>
```

## Where the contract is applied

The anatomy contract is resolved by `resolveBlockAnatomy` in `@plone/helpers`.

It is consumed by:

- `BlockAnatomyPlugin` in `@plone/plate` for Plate-native blocks and registry-backed Plone blocks in Plate/Somersault rendering
- `BlockWrapper` in `@plone/layout` for Plone Volto block fallback rendering (not-Plate-native, non-Somersault)

This avoids duplicating class-name rules in individual blocks.

Plone Volto block rendering and Somersault editor rendering are separate paths.
Plone Volto rendering uses `BlockWrapper`.
Somersault editor rendering goes through Plate and receives the same classes from `BlockAnatomyPlugin`.

## Plate-native block categories

Plate-native block categories are configured in `config.blocks.plateBlocksConfig`.

```ts
config.blocks.plateBlocksConfig = {
p: {
category: 'text',
blockWidth: {
defaultWidth: 'narrow',
widths: ['narrow'],
},
},
toc: {
category: 'navigation',
blockWidth: {
defaultWidth: 'default',
widths: ['layout', 'default', 'narrow'],
},
},
};
```

Registry-backed Plone blocks use the `category` from `config.blocks.blocksConfig`.

## Registry-backed Plone blocks in Plate

Registry-backed Plone blocks embedded in Plate use the `ploneBlock` node type.
The underlying Plone block type is stored in `@type`.

```ts
{
type: 'ploneBlock',
'@type': 'image',
children: [{ text: '' }],
}
```

`BlockAnatomyPlugin` resolves this as block type `image`, not `ploneBlock`.

## Style fields are separate

Block anatomy controls DOM classes and data attributes.
Style fields control CSS custom properties.

For example:

- `BlockAnatomyPlugin` adds `.block.block-teaser.category-teaser`
- `StyleFieldsPlugin` adds styles such as `--theme-color` or `--block-width`

Keep these responsibilities separate when adding new styling behavior.
63 changes: 32 additions & 31 deletions docs/development/configure-editor-block-widths.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ myst:

This guide explains the current block width model in the Plate editor, including how shared widths are defined, how width policies are configured for Plate blocks and Plone blocks, and how the selected width is injected into rendered block styles.

For the block class-name and data-attribute contract, see {doc}`block-anatomy`.

## How it works

The block width system is implemented by `BlockWidthPlugin` in `packages/plate/components/editor/plugins/block-width-plugin.ts`.
The editor width toolbar and Plate-native width defaults are implemented by `BlockWidthPlugin` in `packages/plate/components/editor/plugins/block-width-plugin.ts`.
Registry-backed Plone block width styles are resolved through the generic style-field runtime, using `blockWidth` as a bridged style field.

The current shape is:

- Widths are stored on block nodes as semantic ids such as `narrow`, `default`, `layout`, and `full`.
- The available width definitions come from `config.blocks.widths`.
- Each width definition is a `StyleDefinition`, so it can inject a full style object.
- The selected width is resolved to a style object and merged into the Plate element `style` prop.
- The selected width is resolved to a style object and merged into the block element `style` prop.
- The toolbar uses the active block policy to show only the widths allowed for that block.
- Normalization ensures a block always has a valid `blockWidth` value.
- If a block does not define its own `defaultWidth`, the plugin resolves it from `config.blocks.widths`.
Expand Down Expand Up @@ -77,7 +80,7 @@ Otherwise, the first item in `config.blocks.widths` becomes the shared default.

### How styles are injected

The plugin resolves the current `blockWidth` id against `config.blocks.widths`, then injects the matching `style` object into the Plate element.
The runtime resolves the current `blockWidth` id against `config.blocks.widths`, then injects the matching `style` object into the block element.

That means this width:

Expand All @@ -104,8 +107,8 @@ The layout CSS consumes that variable in `packages/layout/styles/content-area.cs
So the flow is:

1. The node stores `blockWidth: 'layout'`.
2. The plugin resolves `layout` in `config.blocks.widths`.
3. The plugin injects `style={{ '--block-width': 'var(--layout-container-width)' }}`.
2. The runtime resolves `layout` in `config.blocks.widths`.
3. The runtime injects `style={{ '--block-width': 'var(--layout-container-width)' }}`.
4. CSS uses `var(--block-width)` to compute the final `max-width`.

## Configure widths for Plate blocks
Expand Down Expand Up @@ -161,47 +164,45 @@ The `blockWidth` policy supports:

## Configure widths for Plone blocks

Plone blocks are configured in their block info object under `packages/blocks/<Block>/index.ts`.
Plone blocks (non-plate native, registry-backed) are configured in their block schema.

Example from `packages/blocks/Image/index.ts`:
Example from `packages/blocks/Image/schema.tsx`:

```ts
const ImageBlockInfo = {
id: 'image',
title: 'Image',
// ...
blockWidth: {
defaultWidth: 'default',
widths: ['layout', 'default', 'narrow', 'full'],
},
};
blockWidth: {
title: 'Block width',
widget: 'width',
default: 'default',
styleField: true,
},
```

This value is registered through `config.blocks.blocksConfig`, so the width plugin can resolve it for adapted Plone blocks.
This stores the selected width id in the block data as `blockWidth`.
Because the field is marked with `styleField: true`, `StyleFieldsPlugin` can resolve that stored id to the matching style definition from `config.blocks.widths`.

To configure another Plone block, add a `blockWidth` section to its block info object:
To configure another Plone block, add a `blockWidth` property to its schema:

```ts
const MyBlockInfo = {
id: 'myBlock',
title: 'My block',
// ...
properties: {
blockWidth: {
defaultWidth: 'default',
widths: ['default', 'narrow'],
title: 'Block width',
widget: 'width',
default: 'default',
actions = ['narrow', 'default'],
styleField: true,
},
};
}
```

## Resolution order

The width plugin resolves the active block policy from the registry:
Width policy resolution is split by block family:

- For Plate blocks, it reads `config.blocks.plateBlocksConfig[element.type]`.
- For adapted Plone blocks, it reads `config.blocks.blocksConfig[element['@type']]`.
- If no registry config is found, it falls back to plugin options for backward compatibility.
- For Plate-native blocks, `BlockWidthPlugin` reads `config.blocks.plateBlocksConfig[element.type]`.
- For Plone blocks, the style-field runtime reads fields marked with `styleField` from `config.blocks.blocksConfig[element['@type']].blockSchema`.
- If no Plate-native registry config is found, `BlockWidthPlugin` falls back to plugin options for backward compatibility.

The toolbar uses the resolved policy and the shared width definitions together:
For Plate-native blocks, the width toolbar uses the resolved policy and the shared width definitions together:

- the policy determines which width ids are allowed
- `config.blocks.widths` determines the labels and injected styles for those ids
Expand All @@ -210,7 +211,7 @@ The toolbar uses the resolved policy and the shared width definitions together:
```{note}
Widths are stored in the node as `blockWidth`.
Width values should be semantic ids such as `narrow` or `layout`, not raw CSS values.
The `BlockWidthPlugin` normalizes blocks to ensure `blockWidth` is set and valid for the current block.
The `BlockWidthPlugin` normalizes Plate-native blocks to ensure `blockWidth` is set and valid for the current block.
The toolbar options are sourced from `config.blocks.widths`.
The actual visual width is controlled by CSS through `--block-width`.
Registry-based configuration is now the preferred approach for both Plate and Plone blocks.
Expand Down
1 change: 1 addition & 0 deletions docs/development/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ This part of the documentation describes how to develop projects using Plone Aur
images
i18n
editor-slash-menu
block-anatomy
configure-editor-block-widths
```
Loading
Loading