Skip to content
Merged
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
144 changes: 126 additions & 18 deletions packages/editor/src/components/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,49 @@ import { modalName as patternRenameModalName } from '../pattern-rename-modal';
import { modalName as patternDuplicateModalName } from '../pattern-duplicate-modal';
import isTemplateRevertable from '../../store/utils/is-template-revertable';

/**
* Returns the command that toggles content-only editing for patterns and template parts.
* The command is registered both globally for search and contextually for block
* selection, so keeping it in one place ensures the label and callback stay aligned.
*
* @param {Object} options Command options.
* @param {boolean} options.disableContentOnlyForPatternsAndTemplateParts Whether content-only editing is disabled for patterns and template parts.
* @param {Function} options.stopEditingContentOnlySection Stops editing the current content-only section before changing the setting.
* @param {Function} options.updateEditorSettings Updates the editor settings.
* @return {Object} The command configuration.
*/
function getTogglePatternEditingCommand( {
disableContentOnlyForPatternsAndTemplateParts,
stopEditingContentOnlySection,
updateEditorSettings,
} ) {
return {
name: 'core/toggle-pattern-editing',
label: disableContentOnlyForPatternsAndTemplateParts
? __( 'Disable editing all patterns' )
: __( 'Enable editing all patterns' ),
icon: symbol,
category: 'command',
callback: ( { close } ) => {
const disableContentOnly =
! disableContentOnlyForPatternsAndTemplateParts;
stopEditingContentOnlySection();
updateEditorSettings( {
disableContentOnlyForUnsyncedPatterns: disableContentOnly,
disableContentOnlyForTemplateParts: disableContentOnly,
} );
close();
},
};
}

function isPatternOrTemplatePartBlock( blockName, attributes ) {
return (
!! attributes?.metadata?.patternName ||
blockName === 'core/template-part'
);
}

const getEditorCommandLoader = () =>
function useEditorCommandLoader() {
const {
Expand Down Expand Up @@ -191,24 +234,13 @@ const getEditorCommandLoader = () =>
},
} );

commands.push( {
name: 'core/toggle-pattern-editing',
label: disableContentOnlyForPatternsAndTemplateParts
? __( 'Disable editing all patterns' )
: __( 'Enable editing all patterns' ),
icon: symbol,
category: 'command',
callback: ( { close } ) => {
const disableContentOnly =
! disableContentOnlyForPatternsAndTemplateParts;
stopEditingContentOnlySection();
updateEditorSettings( {
disableContentOnlyForUnsyncedPatterns: disableContentOnly,
disableContentOnlyForTemplateParts: disableContentOnly,
} );
close();
},
} );
commands.push(
getTogglePatternEditingCommand( {
disableContentOnlyForPatternsAndTemplateParts,
stopEditingContentOnlySection,
updateEditorSettings,
} )
);

if ( allowSwitchEditorMode ) {
commands.push( {
Expand Down Expand Up @@ -324,6 +356,76 @@ const getEditorCommandLoader = () =>
};
};

const getPatternEditingContextualCommands = () =>
function usePatternEditingContextualCommands( { search } ) {
const {
disableContentOnlyForPatternsAndTemplateParts,
hasPatternOrTemplatePartSelection,
isPreviewMode,
} = useSelect( ( select ) => {
const {
getBlockAttributes,
getBlockName,
getBlockParents,
getSelectedBlockClientId,
getSelectedBlockClientIds,
getSettings,
} = select( blockEditorStore );
const { getEditorSettings } = select( editorStore );
const editorSettings = getEditorSettings();
const selectedBlockClientId = getSelectedBlockClientId();
const selectedBlockClientIds = getSelectedBlockClientIds();
const clientIdsToCheck =
selectedBlockClientId && selectedBlockClientIds.length === 1
? [
selectedBlockClientId,
...getBlockParents( selectedBlockClientId, true ),
]
: [];

return {
disableContentOnlyForPatternsAndTemplateParts:
!! editorSettings.disableContentOnlyForUnsyncedPatterns &&
!! editorSettings.disableContentOnlyForTemplateParts,
hasPatternOrTemplatePartSelection: clientIdsToCheck.some(
( clientId ) =>
isPatternOrTemplatePartBlock(
getBlockName( clientId ),
getBlockAttributes( clientId )
)
),
isPreviewMode: getSettings().isPreviewMode,
};
}, [] );
const { updateEditorSettings } = useDispatch( editorStore );
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const { stopEditingContentOnlySection } = unlock(
useDispatch( blockEditorStore )
);

// Keep the disable command available after full pattern editing is enabled,
// even when the current selection is no longer inside a pattern or template part.
if (
search ||
( ! hasPatternOrTemplatePartSelection &&
! disableContentOnlyForPatternsAndTemplateParts ) ||
isPreviewMode
) {
return { isLoading: false, commands: [] };
}

return {
isLoading: false,
commands: [
getTogglePatternEditingCommand( {
disableContentOnlyForPatternsAndTemplateParts,
stopEditingContentOnlySection,
updateEditorSettings,
} ),
],
Comment on lines +419 to +425

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contextual commands are in packages/editor because the command mutates editor-level settings via editorStore.updateEditorSettings.

I noticed most of the contextual commands are in packages/block-editor/src/components/use-block-commands/index.js, but that doesn't depend on any editor store settings/actions.

};
};

const getEditedEntityContextualCommands = () =>
function useEditedEntityContextualCommands() {
const { postType } = useSelect( ( select ) => {
Expand Down Expand Up @@ -514,6 +616,12 @@ export default function useCommands() {
context: 'entity-edit',
} );

useCommandLoader( {
name: 'core/editor/pattern-editing-contextual-commands',
hook: getPatternEditingContextualCommands(),
context: 'block-selection-edit',
} );

useCommandLoader( {
name: 'core/editor/page-content-focus',
hook: getPageContentFocusCommands(),
Expand Down
62 changes: 62 additions & 0 deletions test/e2e/specs/editor/various/patterns.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,68 @@ test.describe( 'Unsynced pattern', () => {
await expect( blockHeading ).toHaveAccessibleName( 'Header Group' );
} );

test( 'shows the full pattern editing command suggestion when a pattern content block is selected', async ( {
editor,
page,
pageUtils,
} ) => {
await editor.setContent( `<!-- wp:group {"metadata":{"patternName":"theme/header-wrapper","name":"Header"},"layout":{"type":"constrained"}} -->
<div class="wp-block-group"><!-- wp:paragraph -->
<p>Pattern content</p>
<!-- /wp:paragraph --></div>
<!-- /wp:group -->
<!-- wp:paragraph -->
<p>Outside paragraph</p>
<!-- /wp:paragraph -->` );

await editor.canvas
.getByRole( 'document', { name: 'Block: Paragraph' } )
.filter( { hasText: 'Pattern content' } )
.click();

await pageUtils.pressKeys( 'primary+k' );
const commandSuggestions = page.getByRole( 'listbox', {
name: 'Command suggestions',
} );

await expect(
commandSuggestions.getByText( 'Suggestions' )
).toBeVisible();
await expect(
commandSuggestions.getByRole( 'option', {
name: 'Enable editing all patterns',
} )
).toBeVisible();

await commandSuggestions
.getByRole( 'option', {
name: 'Enable editing all patterns',
} )
.click();
await expect( commandSuggestions ).toBeHidden();
await page.evaluate( () => {
window.wp.data
.dispatch( 'core/preferences' )
.set( 'core/commands', 'recentlyUsed', [] );
} );
await editor.canvas
.getByRole( 'document', { name: 'Block: Paragraph' } )
.filter( { hasText: 'Outside paragraph' } )
.click();

await pageUtils.pressKeys( 'primary+k' );

await expect( commandSuggestions.getByText( 'Recent' ) ).toBeHidden();
await expect(
commandSuggestions.getByText( 'Suggestions' )
).toBeVisible();
await expect(
commandSuggestions.getByRole( 'option', {
name: 'Disable editing all patterns',
} )
).toBeVisible();
} );

test( 'shows the source block in the inspector when full pattern editing is enabled', async ( {
editor,
page,
Expand Down
Loading