From 95357ace4b7e797115b77f74b09a5fbefe0d665d Mon Sep 17 00:00:00 2001 From: Paul Popus Date: Thu, 20 Feb 2025 13:21:11 +0000 Subject: [PATCH 1/4] feat: allow disabling the blockname field via admin.blockName --- packages/payload/src/fields/config/client.ts | 7 +++++++ packages/payload/src/fields/config/types.ts | 3 ++- packages/ui/src/fields/Blocks/BlockRow.tsx | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/payload/src/fields/config/client.ts b/packages/payload/src/fields/config/client.ts index 8dfa46d7664..11f1db0157c 100644 --- a/packages/payload/src/fields/config/client.ts +++ b/packages/payload/src/fields/config/client.ts @@ -122,6 +122,13 @@ export const createClientBlocks = ({ clientBlock.jsx = jsxResolved } + if (!clientBlock?.admin?.blockName) { + clientBlock.admin = { + ...clientBlock.admin, + blockName: typeof block?.admin?.blockName === 'undefined' ? true : block.admin.blockName, + } + } + if (block.labels) { clientBlock.labels = {} as unknown as LabelsClient diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index af914d1b19f..ec0fcc75f59 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -1364,6 +1364,7 @@ export type Block = { */ _sanitized?: boolean admin?: { + blockName?: boolean components?: { /** * This will replace the entire block component, including the block header / collapsible. @@ -1401,7 +1402,7 @@ export type Block = { } export type ClientBlock = { - admin?: Pick + admin?: Pick fields: ClientField[] labels?: LabelsClient } & Pick diff --git a/packages/ui/src/fields/Blocks/BlockRow.tsx b/packages/ui/src/fields/Blocks/BlockRow.tsx index c3bdba4271b..d86aaa59184 100644 --- a/packages/ui/src/fields/Blocks/BlockRow.tsx +++ b/packages/ui/src/fields/Blocks/BlockRow.tsx @@ -155,7 +155,9 @@ export const BlockRow: React.FC = ({ > {getTranslation(block.labels.singular, i18n)} - + {block.admin.blockName && ( + + )} {fieldHasErrors && } )} From e6ee8c471b20b416cff0f94f59d956566b521ed4 Mon Sep 17 00:00:00 2001 From: Paul Popus Date: Thu, 20 Feb 2025 15:01:10 +0000 Subject: [PATCH 2/4] add tests --- test/fields/collections/Blocks/e2e.spec.ts | 29 +++++++++- test/fields/collections/Blocks/index.ts | 13 +++++ test/fields/collections/Blocks/shared.ts | 4 ++ test/fields/payload-types.ts | 67 ++++++++++++++++++++-- 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/test/fields/collections/Blocks/e2e.spec.ts b/test/fields/collections/Blocks/e2e.spec.ts index d012727a8d0..835d6c1ac57 100644 --- a/test/fields/collections/Blocks/e2e.spec.ts +++ b/test/fields/collections/Blocks/e2e.spec.ts @@ -15,7 +15,7 @@ import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js' import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js' import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { RESTClient } from '../../../helpers/rest.js' -import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' +import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) @@ -344,4 +344,31 @@ describe('Block fields', () => { expect(await field.count()).toEqual(0) }) }) + + describe('blockNames', () => { + test('should show blockName field', async () => { + await page.goto(url.create) + + const blockWithBlockname = page.locator('#field-blocks .blocks-field__rows #blocks-row-1') + + const blocknameField = blockWithBlockname.locator('.section-title') + + await expect(async () => await expect(blocknameField).toBeVisible()).toPass({ + timeout: POLL_TOPASS_TIMEOUT, + }) + + await expect(blocknameField).toHaveAttribute('data-value', 'Second block') + }) + + test("should not show blockName field when it's disabled", async () => { + await page.goto(url.create) + const blockWithBlockname = page.locator('#field-blocks .blocks-field__rows #blocks-row-3') + + await expect( + async () => await expect(blockWithBlockname.locator('.section-title')).toBeHidden(), + ).toPass({ + timeout: POLL_TOPASS_TIMEOUT, + }) + }) + }) }) diff --git a/test/fields/collections/Blocks/index.ts b/test/fields/collections/Blocks/index.ts index db93506f283..080ac42fd90 100644 --- a/test/fields/collections/Blocks/index.ts +++ b/test/fields/collections/Blocks/index.ts @@ -30,6 +30,19 @@ export const getBlocksField = (prefix?: string): BlocksField => ({ }, ], }, + { + slug: prefix ? `${prefix}NoBlockname` : 'noBlockname', + interfaceName: prefix ? `${prefix}NoBlockname` : 'NoBlockname', + admin: { + blockName: false, + }, + fields: [ + { + name: 'text', + type: 'text', + }, + ], + }, { slug: prefix ? `${prefix}Number` : 'number', interfaceName: prefix ? `${prefix}NumberBlock` : 'NumberBlock', diff --git a/test/fields/collections/Blocks/shared.ts b/test/fields/collections/Blocks/shared.ts index 6087295831d..b1326d829ce 100644 --- a/test/fields/collections/Blocks/shared.ts +++ b/test/fields/collections/Blocks/shared.ts @@ -32,6 +32,10 @@ export const getBlocksFieldSeedData = (prefix?: string): any => [ }, ], }, + { + blockType: prefix ? `${prefix}NoBlockname` : 'noBlockname', + text: 'Hello world', + }, ] export const blocksDoc: Partial = { diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 065c473c8d6..8bdffa1ac6e 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -699,16 +699,29 @@ export interface ArrayField { */ export interface BlockField { id: string; - blocks: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[]; - duplicate: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[]; + blocks: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[]; + duplicate: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[]; collapsedByDefaultBlocks: ( | LocalizedContentBlock + | LocalizedNoBlockname + | LocalizedNumberBlock + | LocalizedSubBlocksBlock + | LocalizedTabsBlock + )[]; + disableSort: ( + | LocalizedContentBlock + | LocalizedNoBlockname + | LocalizedNumberBlock + | LocalizedSubBlocksBlock + | LocalizedTabsBlock + )[]; + localizedBlocks: ( + | LocalizedContentBlock + | LocalizedNoBlockname | LocalizedNumberBlock | LocalizedSubBlocksBlock | LocalizedTabsBlock )[]; - disableSort: (LocalizedContentBlock | LocalizedNumberBlock | LocalizedSubBlocksBlock | LocalizedTabsBlock)[]; - localizedBlocks: (LocalizedContentBlock | LocalizedNumberBlock | LocalizedSubBlocksBlock | LocalizedTabsBlock)[]; i18nBlocks?: | { text?: string | null; @@ -852,6 +865,16 @@ export interface ContentBlock { blockName?: string | null; blockType: 'content'; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "NoBlockname". + */ +export interface NoBlockname { + text: string; + id?: string | null; + blockName?: string | null; + blockType: 'noBlockname'; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "NumberBlock". @@ -908,6 +931,16 @@ export interface LocalizedContentBlock { blockName?: string | null; blockType: 'localizedContent'; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "localizedNoBlockname". + */ +export interface LocalizedNoBlockname { + text: string; + id?: string | null; + blockName?: string | null; + blockType: 'localizedNoBlockname'; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "localizedNumberBlock". @@ -1743,7 +1776,7 @@ export interface TabsField { text: string; id?: string | null; }[]; - blocks: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[]; + blocks: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[]; group: { number: number; }; @@ -2403,6 +2436,7 @@ export interface BlockFieldsSelect { | T | { content?: T | ContentBlockSelect; + noBlockname?: T | NoBlocknameSelect; number?: T | NumberBlockSelect; subBlocks?: T | SubBlocksBlockSelect; tabs?: T | TabsBlockSelect; @@ -2411,6 +2445,7 @@ export interface BlockFieldsSelect { | T | { content?: T | ContentBlockSelect; + noBlockname?: T | NoBlocknameSelect; number?: T | NumberBlockSelect; subBlocks?: T | SubBlocksBlockSelect; tabs?: T | TabsBlockSelect; @@ -2419,6 +2454,7 @@ export interface BlockFieldsSelect { | T | { localizedContent?: T | LocalizedContentBlockSelect; + localizedNoBlockname?: T | LocalizedNoBlocknameSelect; localizedNumber?: T | LocalizedNumberBlockSelect; localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect; localizedTabs?: T | LocalizedTabsBlockSelect; @@ -2427,6 +2463,7 @@ export interface BlockFieldsSelect { | T | { localizedContent?: T | LocalizedContentBlockSelect; + localizedNoBlockname?: T | LocalizedNoBlocknameSelect; localizedNumber?: T | LocalizedNumberBlockSelect; localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect; localizedTabs?: T | LocalizedTabsBlockSelect; @@ -2435,6 +2472,7 @@ export interface BlockFieldsSelect { | T | { localizedContent?: T | LocalizedContentBlockSelect; + localizedNoBlockname?: T | LocalizedNoBlocknameSelect; localizedNumber?: T | LocalizedNumberBlockSelect; localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect; localizedTabs?: T | LocalizedTabsBlockSelect; @@ -2600,6 +2638,15 @@ export interface ContentBlockSelect { id?: T; blockName?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "NoBlockname_select". + */ +export interface NoBlocknameSelect { + text?: T; + id?: T; + blockName?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "NumberBlock_select". @@ -2649,6 +2696,15 @@ export interface LocalizedContentBlockSelect { id?: T; blockName?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "localizedNoBlockname_select". + */ +export interface LocalizedNoBlocknameSelect { + text?: T; + id?: T; + blockName?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "localizedNumberBlock_select". @@ -3295,6 +3351,7 @@ export interface TabsFieldsSelect { | T | { content?: T | ContentBlockSelect; + noBlockname?: T | NoBlocknameSelect; number?: T | NumberBlockSelect; subBlocks?: T | SubBlocksBlockSelect; tabs?: T | TabsBlockSelect; From c62be2d8dbd230937bc723faad50d933a07dc11a Mon Sep 17 00:00:00 2001 From: Paul Popus Date: Thu, 20 Feb 2025 15:14:25 +0000 Subject: [PATCH 3/4] add blockName in sanitisation too --- .../src/fields/config/sanitize.spec.ts | 67 +++++++++++++++++++ .../payload/src/fields/config/sanitize.ts | 8 +++ 2 files changed, 75 insertions(+) diff --git a/packages/payload/src/fields/config/sanitize.spec.ts b/packages/payload/src/fields/config/sanitize.spec.ts index 5bd1ef17a0b..6cc10b4b52d 100644 --- a/packages/payload/src/fields/config/sanitize.spec.ts +++ b/packages/payload/src/fields/config/sanitize.spec.ts @@ -362,4 +362,71 @@ describe('sanitizeFields', () => { expect(sanitizedFields).toStrictEqual([]) }) }) + describe('blocks', () => { + it('should maintain admin.blockName false after sanitization', async () => { + const fields: Field[] = [ + { + name: 'noLabelBlock', + type: 'blocks', + blocks: [ + { + slug: 'number', + admin: { + blockName: false, + }, + fields: [ + { + name: 'testNumber', + type: 'number', + }, + ], + }, + ], + label: false, + }, + ] + const sanitizedField = ( + await sanitizeFields({ + config, + fields, + validRelationships: [], + }) + )[0] as BlocksField + + const sanitizedBlock = sanitizedField.blocks[0] + + expect(sanitizedBlock.admin?.blockName).toStrictEqual(false) + }) + it('should default admin.blockName to true after sanitization', async () => { + const fields: Field[] = [ + { + name: 'noLabelBlock', + type: 'blocks', + blocks: [ + { + slug: 'number', + fields: [ + { + name: 'testNumber', + type: 'number', + }, + ], + }, + ], + label: false, + }, + ] + const sanitizedField = ( + await sanitizeFields({ + config, + fields, + validRelationships: [], + }) + )[0] as BlocksField + + const sanitizedBlock = sanitizedField.blocks[0] + + expect(sanitizedBlock.admin?.blockName).toStrictEqual(true) + }) + }) }) diff --git a/packages/payload/src/fields/config/sanitize.ts b/packages/payload/src/fields/config/sanitize.ts index 127bb56fe7c..1047361923e 100644 --- a/packages/payload/src/fields/config/sanitize.ts +++ b/packages/payload/src/fields/config/sanitize.ts @@ -258,6 +258,14 @@ export const sanitizeFields = async ({ richTextSanitizationPromises, validRelationships, }) + + if (!block?.admin?.blockName) { + block.admin = { + ...block.admin, + blockName: + typeof block?.admin?.blockName === 'undefined' ? true : block.admin.blockName, + } + } } } From 381794c3e752c2cd6f0cba1332d5aa1a2395f7bc Mon Sep 17 00:00:00 2001 From: Paul Popus Date: Thu, 20 Feb 2025 17:01:39 +0000 Subject: [PATCH 4/4] fix e2e --- test/fields/collections/Blocks/e2e.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/fields/collections/Blocks/e2e.spec.ts b/test/fields/collections/Blocks/e2e.spec.ts index 835d6c1ac57..f51c6bf8999 100644 --- a/test/fields/collections/Blocks/e2e.spec.ts +++ b/test/fields/collections/Blocks/e2e.spec.ts @@ -81,7 +81,7 @@ describe('Block fields', () => { const addedRow = page.locator('#field-blocks .blocks-field__row').last() await expect(addedRow).toBeVisible() await expect(addedRow.locator('.blocks-field__block-header')).toHaveText( - 'Custom Block Label: Content 04', + 'Custom Block Label: Content 05', ) }) @@ -155,7 +155,7 @@ describe('Block fields', () => { await duplicateButton.click() const blocks = page.locator('#field-blocks > .blocks-field__rows > div') - expect(await blocks.count()).toEqual(4) + expect(await blocks.count()).toEqual(5) }) test('should save when duplicating subblocks', async () => { @@ -170,7 +170,7 @@ describe('Block fields', () => { await duplicateButton.click() const blocks = page.locator('#field-blocks > .blocks-field__rows > div') - expect(await blocks.count()).toEqual(4) + expect(await blocks.count()).toEqual(5) await page.click('#action-save') await expect(page.locator('.payload-toast-container')).toContainText('successfully')