From 357716dc75fc2a25c07e809132da834ec24ff9de Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sat, 13 Jun 2026 08:46:17 +0200 Subject: [PATCH 1/4] migrate native blocks to somersault --- .../server/content-migrations.server.test.ts | 124 ++++++++++++++++++ .../app/config/server/migrations.server.ts | 26 ++++ .../news/+native-blocks-to-somersault.feature | 1 + 3 files changed, 151 insertions(+) create mode 100644 apps/aurora/app/config/server/content-migrations.server.test.ts create mode 100644 apps/aurora/news/+native-blocks-to-somersault.feature diff --git a/apps/aurora/app/config/server/content-migrations.server.test.ts b/apps/aurora/app/config/server/content-migrations.server.test.ts new file mode 100644 index 000000000..90f99ce8a --- /dev/null +++ b/apps/aurora/app/config/server/content-migrations.server.test.ts @@ -0,0 +1,124 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import config from '@plone/registry'; +import { SOMERSAULT_KEY } from '@plone/plate/constants'; +import type { Content } from '@plone/types'; +import installMigrations from './migrations.server'; +import { migrateContent } from './content-migrations.server'; + +describe('content migrations', () => { + afterEach(() => { + config.settings = {}; + config.blocks = {} as typeof config.blocks; + const utilities = config.utilities as Partial>; + delete utilities.somersaultBlockMigration; + delete utilities.somersaultMigration; + }); + + it('migrates title and legacy value blocks into the somersault block', () => { + config.blocks = { + blocksConfig: {}, + } as typeof config.blocks; + installMigrations(); + + const content: Content = { + '@id': 'http://example.com/', + title: 'Page title', + blocks: { + text: { + '@type': 'slate', + value: [{ type: 'p', children: [{ text: 'First block' }] }], + }, + titleBlock: { + '@type': 'title', + }, + }, + blocks_layout: { + items: ['text', 'titleBlock'], + }, + } as Content; + + const migrated = migrateContent(content); + + expect(migrated.blocks?.[SOMERSAULT_KEY]).toEqual({ + '@type': SOMERSAULT_KEY, + value: [ + { + blockWidth: 'default', + type: 'p', + children: [{ text: 'First block' }], + }, + { + blockWidth: 'default', + type: 'title', + children: [{ text: 'Page title' }], + }, + ], + }); + }); + + it('moves native blocks into the somersault field as unknown nodes', () => { + config.blocks = { + blocksConfig: { + listing: {}, + image: {}, + }, + } as typeof config.blocks; + installMigrations(); + + const content: Content = { + '@id': 'http://example.com/', + title: 'Page title', + blocks: { + titleBlock: { + '@type': 'title', + }, + listing: { + '@type': 'listing', + querystring: { + criteria: [], + }, + }, + image: { + '@type': 'image', + url: '/image', + alt: 'Example image', + }, + custom: { + '@type': 'custom-unregistered', + foo: 'bar', + }, + }, + blocks_layout: { + items: ['titleBlock', 'listing', 'image', 'custom'], + }, + } as Content; + + const migrated = migrateContent(content); + + expect(migrated.blocks?.[SOMERSAULT_KEY]).toEqual({ + '@type': SOMERSAULT_KEY, + value: [ + { + blockWidth: 'default', + type: 'title', + children: [{ text: 'Page title' }], + }, + { + '@type': 'listing', + children: [{ text: '' }], + querystring: { + criteria: [], + }, + type: 'unknown', + }, + { + '@type': 'image', + alt: 'Example image', + children: [{ text: '' }], + type: 'unknown', + url: '/image', + }, + ], + }); + }); +}); diff --git a/apps/aurora/app/config/server/migrations.server.ts b/apps/aurora/app/config/server/migrations.server.ts index 44c3e4500..275dd2086 100644 --- a/apps/aurora/app/config/server/migrations.server.ts +++ b/apps/aurora/app/config/server/migrations.server.ts @@ -12,6 +12,17 @@ import type { SomersaultMigrationArgs, } from '../types'; +const isRegisteredNativeBlock = (block: Record) => { + const blockType = block['@type']; + if (typeof blockType !== 'string') return false; + + const blocksConfig = config.blocks?.blocksConfig as + | Record + | undefined; + + return Boolean(blocksConfig?.[blockType]); +}; + export default function install() { config.registerUtility({ name: 'somersaultBlockMigrationTitle', @@ -38,6 +49,21 @@ export default function install() { Array.isArray(block.value) ? block.value : [], }); + config.registerUtility({ + name: 'somersaultBlockMigrationUnknown', + type: 'somersaultBlockMigration', + method: ({ block }: SomersaultBlockMigrationArgs) => + isRegisteredNativeBlock(block) && !Array.isArray(block.value) + ? [ + { + ...block, + type: 'unknown', + children: [{ text: '' }], + }, + ] + : [], + }); + config.registerUtility({ name: 'somersaultMigrationLegacyBold', type: 'somersaultMigration', diff --git a/apps/aurora/news/+native-blocks-to-somersault.feature b/apps/aurora/news/+native-blocks-to-somersault.feature new file mode 100644 index 000000000..714598b55 --- /dev/null +++ b/apps/aurora/news/+native-blocks-to-somersault.feature @@ -0,0 +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 From 249fc6914f14aa6970cecf576e98ff9ff9edba7f Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sat, 13 Jun 2026 08:49:12 +0200 Subject: [PATCH 2/4] fix test typings --- .../server/content-migrations.server.test.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/aurora/app/config/server/content-migrations.server.test.ts b/apps/aurora/app/config/server/content-migrations.server.test.ts index 90f99ce8a..a6b7ad05e 100644 --- a/apps/aurora/app/config/server/content-migrations.server.test.ts +++ b/apps/aurora/app/config/server/content-migrations.server.test.ts @@ -1,10 +1,19 @@ import { afterEach, describe, expect, it } from 'vitest'; import config from '@plone/registry'; import { SOMERSAULT_KEY } from '@plone/plate/constants'; -import type { Content } from '@plone/types'; +import type { Content, ContentBase } from '@plone/types'; import installMigrations from './migrations.server'; import { migrateContent } from './content-migrations.server'; +type TestContent = Partial & { + '@id': string; + title: string; + blocks: Record; + blocks_layout: { + items: string[]; + }; +}; + describe('content migrations', () => { afterEach(() => { config.settings = {}; @@ -20,7 +29,7 @@ describe('content migrations', () => { } as typeof config.blocks; installMigrations(); - const content: Content = { + const content: TestContent = { '@id': 'http://example.com/', title: 'Page title', blocks: { @@ -35,9 +44,9 @@ describe('content migrations', () => { blocks_layout: { items: ['text', 'titleBlock'], }, - } as Content; + }; - const migrated = migrateContent(content); + const migrated = migrateContent(content as unknown as Content); expect(migrated.blocks?.[SOMERSAULT_KEY]).toEqual({ '@type': SOMERSAULT_KEY, @@ -65,7 +74,7 @@ describe('content migrations', () => { } as typeof config.blocks; installMigrations(); - const content: Content = { + const content: TestContent = { '@id': 'http://example.com/', title: 'Page title', blocks: { @@ -91,9 +100,9 @@ describe('content migrations', () => { blocks_layout: { items: ['titleBlock', 'listing', 'image', 'custom'], }, - } as Content; + }; - const migrated = migrateContent(content); + const migrated = migrateContent(content as unknown as Content); expect(migrated.blocks?.[SOMERSAULT_KEY]).toEqual({ '@type': SOMERSAULT_KEY, From c8e502ebc6c5a9d5c19e2bc90a665b0c7c85d4be Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sat, 13 Jun 2026 09:11:37 +0200 Subject: [PATCH 3/4] apply width defaults to unknown blocks --- .../server/content-migrations.server.test.ts | 16 ++++++++++++++-- .../editor/plugins/block-width-plugin.test.ts | 6 +++--- .../editor/plugins/block-width-plugin.ts | 18 ++++-------------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/aurora/app/config/server/content-migrations.server.test.ts b/apps/aurora/app/config/server/content-migrations.server.test.ts index a6b7ad05e..620cf5955 100644 --- a/apps/aurora/app/config/server/content-migrations.server.test.ts +++ b/apps/aurora/app/config/server/content-migrations.server.test.ts @@ -68,8 +68,18 @@ describe('content migrations', () => { it('moves native blocks into the somersault field as unknown nodes', () => { config.blocks = { blocksConfig: { - listing: {}, - image: {}, + listing: { + blockWidth: { + defaultWidth: 'layout', + widths: ['layout'], + }, + }, + image: { + blockWidth: { + defaultWidth: 'full', + widths: ['full'], + }, + }, }, } as typeof config.blocks; installMigrations(); @@ -114,6 +124,7 @@ describe('content migrations', () => { }, { '@type': 'listing', + blockWidth: 'layout', children: [{ text: '' }], querystring: { criteria: [], @@ -123,6 +134,7 @@ describe('content migrations', () => { { '@type': 'image', alt: 'Example image', + blockWidth: 'full', children: [{ text: '' }], type: 'unknown', url: '/image', diff --git a/packages/plate/components/editor/plugins/block-width-plugin.test.ts b/packages/plate/components/editor/plugins/block-width-plugin.test.ts index 2a05176df..279b8877d 100644 --- a/packages/plate/components/editor/plugins/block-width-plugin.test.ts +++ b/packages/plate/components/editor/plugins/block-width-plugin.test.ts @@ -141,7 +141,7 @@ describe('block width plugin', () => { }); }); - it('does not use blocksConfig.blockWidth for unknown blocks in BlockWidthPlugin', () => { + it('uses blocksConfig.blockWidth for unknown blocks in BlockWidthPlugin', () => { registryBlocks.widths = [ { name: 'default', @@ -172,8 +172,8 @@ describe('block width plugin', () => { children: [{ text: '' }], } as any), ).toEqual({ - defaultWidth: 'default', - widths: ['default', 'layout'], + defaultWidth: 'layout', + widths: ['layout'], }); }); diff --git a/packages/plate/components/editor/plugins/block-width-plugin.ts b/packages/plate/components/editor/plugins/block-width-plugin.ts index 63670b521..e33619a69 100644 --- a/packages/plate/components/editor/plugins/block-width-plugin.ts +++ b/packages/plate/components/editor/plugins/block-width-plugin.ts @@ -114,10 +114,6 @@ export const resolveBlockWidthConfig = ( editor: SlateEditor, element?: TElement | null, ): BlockWidthConfig => { - if (element?.type === 'unknown') { - return {}; - } - const registryConfig = element?.type === 'unknown' ? getPloneBlockRegistryWidthConfig(element) @@ -172,16 +168,10 @@ export const applyBlockWidthDefaultsInValue = (value: unknown[]) => { const element = node as ValueElement; if (typeof element.type !== 'string') return; - if (element.type === 'unknown') { - if (Array.isArray(element.children)) { - element.children.forEach(visit); - } - return; - } - - const registryConfig = getPlateBlockRegistryWidthConfig( - element as TElement, - ); + const registryConfig = + element.type === 'unknown' + ? getPloneBlockRegistryWidthConfig(element as TElement) + : getPlateBlockRegistryWidthConfig(element as TElement); const defaultWidth = registryConfig.defaultWidth ?? fallbackDefaultWidth; const widths = registryConfig.widths?.length ? registryConfig.widths From 31376d3a91c102062d276f4b681eefbd60211c16 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sat, 13 Jun 2026 09:16:10 +0200 Subject: [PATCH 4/4] Revert "apply width defaults to unknown blocks" This reverts commit c8e502ebc6c5a9d5c19e2bc90a665b0c7c85d4be. --- .../server/content-migrations.server.test.ts | 16 ++-------------- .../editor/plugins/block-width-plugin.test.ts | 6 +++--- .../editor/plugins/block-width-plugin.ts | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/aurora/app/config/server/content-migrations.server.test.ts b/apps/aurora/app/config/server/content-migrations.server.test.ts index 620cf5955..a6b7ad05e 100644 --- a/apps/aurora/app/config/server/content-migrations.server.test.ts +++ b/apps/aurora/app/config/server/content-migrations.server.test.ts @@ -68,18 +68,8 @@ describe('content migrations', () => { it('moves native blocks into the somersault field as unknown nodes', () => { config.blocks = { blocksConfig: { - listing: { - blockWidth: { - defaultWidth: 'layout', - widths: ['layout'], - }, - }, - image: { - blockWidth: { - defaultWidth: 'full', - widths: ['full'], - }, - }, + listing: {}, + image: {}, }, } as typeof config.blocks; installMigrations(); @@ -124,7 +114,6 @@ describe('content migrations', () => { }, { '@type': 'listing', - blockWidth: 'layout', children: [{ text: '' }], querystring: { criteria: [], @@ -134,7 +123,6 @@ describe('content migrations', () => { { '@type': 'image', alt: 'Example image', - blockWidth: 'full', children: [{ text: '' }], type: 'unknown', url: '/image', diff --git a/packages/plate/components/editor/plugins/block-width-plugin.test.ts b/packages/plate/components/editor/plugins/block-width-plugin.test.ts index 279b8877d..2a05176df 100644 --- a/packages/plate/components/editor/plugins/block-width-plugin.test.ts +++ b/packages/plate/components/editor/plugins/block-width-plugin.test.ts @@ -141,7 +141,7 @@ describe('block width plugin', () => { }); }); - it('uses blocksConfig.blockWidth for unknown blocks in BlockWidthPlugin', () => { + it('does not use blocksConfig.blockWidth for unknown blocks in BlockWidthPlugin', () => { registryBlocks.widths = [ { name: 'default', @@ -172,8 +172,8 @@ describe('block width plugin', () => { children: [{ text: '' }], } as any), ).toEqual({ - defaultWidth: 'layout', - widths: ['layout'], + defaultWidth: 'default', + widths: ['default', 'layout'], }); }); diff --git a/packages/plate/components/editor/plugins/block-width-plugin.ts b/packages/plate/components/editor/plugins/block-width-plugin.ts index e33619a69..63670b521 100644 --- a/packages/plate/components/editor/plugins/block-width-plugin.ts +++ b/packages/plate/components/editor/plugins/block-width-plugin.ts @@ -114,6 +114,10 @@ export const resolveBlockWidthConfig = ( editor: SlateEditor, element?: TElement | null, ): BlockWidthConfig => { + if (element?.type === 'unknown') { + return {}; + } + const registryConfig = element?.type === 'unknown' ? getPloneBlockRegistryWidthConfig(element) @@ -168,10 +172,16 @@ export const applyBlockWidthDefaultsInValue = (value: unknown[]) => { const element = node as ValueElement; if (typeof element.type !== 'string') return; - const registryConfig = - element.type === 'unknown' - ? getPloneBlockRegistryWidthConfig(element as TElement) - : getPlateBlockRegistryWidthConfig(element as TElement); + if (element.type === 'unknown') { + if (Array.isArray(element.children)) { + element.children.forEach(visit); + } + return; + } + + const registryConfig = getPlateBlockRegistryWidthConfig( + element as TElement, + ); const defaultWidth = registryConfig.defaultWidth ?? fallbackDefaultWidth; const widths = registryConfig.widths?.length ? registryConfig.widths