feat: allow hiding the blockName field visible in blocks' headers via admin.disableBlockName (#11301)
Adds a new `admin.disableBlockName` property that allows you to disable the blockName field entirely in the admin view. It defaults to false for backwards compatibility.
This commit is contained in:
@@ -84,6 +84,7 @@ The Blocks Field inherits all of the default options from the base [Field Admin
|
||||
| **`group`** | Text or localization object used to group this Block in the Blocks Drawer. |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| **`isSortable`** | Disable order sorting by setting this value to `false` |
|
||||
| **`disableBlockName`** | Hide the blockName field by setting this value to `true` |
|
||||
|
||||
#### Customizing the way your block is rendered in Lexical
|
||||
|
||||
@@ -165,7 +166,7 @@ The `blockType` is saved as the slug of the block that has been selected.
|
||||
|
||||
**`blockName`**
|
||||
|
||||
The Admin Panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability.
|
||||
The Admin Panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability. This can be visually hidden via `admin.disableBlockName`.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -126,6 +126,15 @@ export const createClientBlocks = ({
|
||||
clientBlock.jsx = jsxResolved
|
||||
}
|
||||
|
||||
if (block?.admin?.disableBlockName) {
|
||||
// Check for existing admin object, this way we don't have to spread it in
|
||||
if (clientBlock.admin) {
|
||||
clientBlock.admin.disableBlockName = block.admin.disableBlockName
|
||||
} else {
|
||||
clientBlock.admin = { disableBlockName: block.admin.disableBlockName }
|
||||
}
|
||||
}
|
||||
|
||||
if (block.labels) {
|
||||
clientBlock.labels = {} as unknown as LabelsClient
|
||||
|
||||
|
||||
@@ -362,4 +362,71 @@ describe('sanitizeFields', () => {
|
||||
expect(sanitizedFields).toStrictEqual([])
|
||||
})
|
||||
})
|
||||
describe('blocks', () => {
|
||||
it('should maintain admin.blockName true after sanitization', async () => {
|
||||
const fields: Field[] = [
|
||||
{
|
||||
name: 'noLabelBlock',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'number',
|
||||
admin: {
|
||||
disableBlockName: true,
|
||||
},
|
||||
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?.disableBlockName).toStrictEqual(true)
|
||||
})
|
||||
it('should default admin.disableBlockName 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?.disableBlockName).toStrictEqual(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1378,6 +1378,12 @@ export type Block = {
|
||||
}
|
||||
/** Extension point to add your custom data. Available in server and client. */
|
||||
custom?: Record<string, any>
|
||||
/**
|
||||
* Hides the block name field from the Block's header
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disableBlockName?: boolean
|
||||
group?: Record<string, string> | string
|
||||
jsx?: PayloadComponent
|
||||
}
|
||||
@@ -1407,7 +1413,7 @@ export type Block = {
|
||||
}
|
||||
|
||||
export type ClientBlock = {
|
||||
admin?: Pick<Block['admin'], 'custom' | 'group'>
|
||||
admin?: Pick<Block['admin'], 'custom' | 'disableBlockName' | 'group'>
|
||||
fields: ClientField[]
|
||||
labels?: LabelsClient
|
||||
} & Pick<Block, 'imageAltText' | 'imageURL' | 'jsx' | 'slug'>
|
||||
|
||||
@@ -80,6 +80,8 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
|
||||
|
||||
const fieldHasErrors = hasSubmitted && errorCount > 0
|
||||
|
||||
const showBlockName = !block.admin?.disableBlockName
|
||||
|
||||
const classNames = [
|
||||
`${baseClass}__row`,
|
||||
fieldHasErrors ? `${baseClass}__row--has-errors` : `${baseClass}__row--no-errors`,
|
||||
@@ -155,7 +157,7 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
|
||||
>
|
||||
{getTranslation(block.labels.singular, i18n)}
|
||||
</Pill>
|
||||
<SectionTitle path={`${path}.blockName`} readOnly={readOnly} />
|
||||
{showBlockName && <SectionTitle path={`${path}.blockName`} readOnly={readOnly} />}
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,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)
|
||||
@@ -82,7 +82,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',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -156,7 +156,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 () => {
|
||||
@@ -171,7 +171,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')
|
||||
@@ -379,6 +379,33 @@ describe('Block fields', () => {
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('block groups', () => {
|
||||
test('should render group labels', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
@@ -30,6 +30,19 @@ export const getBlocksField = (prefix?: string): BlocksField => ({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: prefix ? `${prefix}NoBlockname` : 'noBlockname',
|
||||
interfaceName: prefix ? `${prefix}NoBlockname` : 'NoBlockname',
|
||||
admin: {
|
||||
disableBlockName: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: prefix ? `${prefix}Number` : 'number',
|
||||
interfaceName: prefix ? `${prefix}NumberBlock` : 'NumberBlock',
|
||||
|
||||
@@ -32,6 +32,10 @@ export const getBlocksFieldSeedData = (prefix?: string): any => [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blockType: prefix ? `${prefix}NoBlockname` : 'noBlockname',
|
||||
text: 'Hello world',
|
||||
},
|
||||
]
|
||||
|
||||
export const blocksDoc: Partial<BlockField> = {
|
||||
|
||||
@@ -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;
|
||||
@@ -883,6 +896,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".
|
||||
@@ -939,6 +962,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".
|
||||
@@ -1801,7 +1834,7 @@ export interface TabsField {
|
||||
text: string;
|
||||
id?: string | null;
|
||||
}[];
|
||||
blocks: (ContentBlock | NumberBlock | SubBlocksBlock | TabsBlock)[];
|
||||
blocks: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[];
|
||||
group: {
|
||||
number: number;
|
||||
};
|
||||
@@ -2461,6 +2494,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
| T
|
||||
| {
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
noBlockname?: T | NoBlocknameSelect<T>;
|
||||
number?: T | NumberBlockSelect<T>;
|
||||
subBlocks?: T | SubBlocksBlockSelect<T>;
|
||||
tabs?: T | TabsBlockSelect<T>;
|
||||
@@ -2469,6 +2503,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
| T
|
||||
| {
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
noBlockname?: T | NoBlocknameSelect<T>;
|
||||
number?: T | NumberBlockSelect<T>;
|
||||
subBlocks?: T | SubBlocksBlockSelect<T>;
|
||||
tabs?: T | TabsBlockSelect<T>;
|
||||
@@ -2477,6 +2512,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
| T
|
||||
| {
|
||||
localizedContent?: T | LocalizedContentBlockSelect<T>;
|
||||
localizedNoBlockname?: T | LocalizedNoBlocknameSelect<T>;
|
||||
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
|
||||
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
|
||||
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
|
||||
@@ -2485,6 +2521,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
| T
|
||||
| {
|
||||
localizedContent?: T | LocalizedContentBlockSelect<T>;
|
||||
localizedNoBlockname?: T | LocalizedNoBlocknameSelect<T>;
|
||||
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
|
||||
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
|
||||
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
|
||||
@@ -2493,6 +2530,7 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
| T
|
||||
| {
|
||||
localizedContent?: T | LocalizedContentBlockSelect<T>;
|
||||
localizedNoBlockname?: T | LocalizedNoBlocknameSelect<T>;
|
||||
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
|
||||
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
|
||||
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
|
||||
@@ -2690,6 +2728,15 @@ export interface ContentBlockSelect<T extends boolean = true> {
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "NoBlockname_select".
|
||||
*/
|
||||
export interface NoBlocknameSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "NumberBlock_select".
|
||||
@@ -2739,6 +2786,15 @@ export interface LocalizedContentBlockSelect<T extends boolean = true> {
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedNoBlockname_select".
|
||||
*/
|
||||
export interface LocalizedNoBlocknameSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedNumberBlock_select".
|
||||
@@ -3404,6 +3460,7 @@ export interface TabsFieldsSelect<T extends boolean = true> {
|
||||
| T
|
||||
| {
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
noBlockname?: T | NoBlocknameSelect<T>;
|
||||
number?: T | NumberBlockSelect<T>;
|
||||
subBlocks?: T | SubBlocksBlockSelect<T>;
|
||||
tabs?: T | TabsBlockSelect<T>;
|
||||
|
||||
Reference in New Issue
Block a user