Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract keyboard shorcuts for heading to paragraph transform and vice-versa #60606

Merged
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
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/block-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@wordpress/icons": "file:../icons",
"@wordpress/interactivity": "file:../interactivity",
"@wordpress/interactivity-router": "file:../interactivity-router",
"@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts",
"@wordpress/keycodes": "file:../keycodes",
"@wordpress/notices": "file:../notices",
"@wordpress/patterns": "file:../patterns",
Expand Down
96 changes: 96 additions & 0 deletions packages/block-library/src/block-keyboard-shortcuts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import {
useShortcut,
store as keyboardShortcutsStore,
} from '@wordpress/keyboard-shortcuts';
import { __ } from '@wordpress/i18n';
import { createBlock } from '@wordpress/blocks';
import { store as blockEditorStore } from '@wordpress/block-editor';

function BlockKeyboardShortcuts() {
Copy link
Contributor

Choose a reason for hiding this comment

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

In this component registration and usage is merged. They were in different components before. The reason is that in some contexts, we want to register the shortcuts (show them in the keyboard shortcuts modal) but "disable" them in some contexts (view mode in the site editor for instance).

I'm not sure whether this creates issues for this PR, it needs to be tested.

Copy link
Contributor

Choose a reason for hiding this comment

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

It seems the Block prefix is redundant here as well, we're already in the block library package.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can rename it but then I'd have to import it 'as' a different name you know, because other components are already named KeyboardShortcuts.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, we do that already for other things. I don't care much though, it's a private API anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this component registration and usage is merged. They were in different components before.

I think they were separated only in edit-site, where the use was here? Not sure that makes a difference, because they are within a check for isEditMode?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, for this PR it probably doesn't change much.

const { registerShortcut } = useDispatch( keyboardShortcutsStore );
const { replaceBlocks } = useDispatch( blockEditorStore );
const { getBlockName, getSelectedBlockClientId, getBlockAttributes } =
useSelect( blockEditorStore );

const handleTransformHeadingAndParagraph = ( event, level ) => {
event.preventDefault();
const destinationBlockName =
level === 0 ? 'core/paragraph' : 'core/heading';
const currentClientId = getSelectedBlockClientId();
if ( currentClientId === null ) {
return;
}
const blockName = getBlockName( currentClientId );
if ( blockName !== 'core/paragraph' && blockName !== 'core/heading' ) {
return;
}
const attributes = getBlockAttributes( currentClientId );
const textAlign =
blockName === 'core/paragraph' ? 'align' : 'textAlign';
const destinationTextAlign =
destinationBlockName === 'core/paragraph' ? 'align' : 'textAlign';

replaceBlocks(
currentClientId,
createBlock( destinationBlockName, {
level,
content: attributes.content,
...{ [ destinationTextAlign ]: attributes[ textAlign ] },
} )
);
};

useEffect( () => {
registerShortcut( {
name: 'core/block-editor/transform-heading-to-paragraph',
category: 'block-library',
description: __( 'Transform heading to paragraph.' ),
keyCombination: {
modifier: 'access',
character: '0',
},
aliases: [
{
modifier: 'access',
character: '7',
},
],
} );

[ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => {
registerShortcut( {
name: `core/block-editor/transform-paragraph-to-heading-${ level }`,
category: 'block-library',
description: __( 'Transform paragraph to heading.' ),
keyCombination: {
modifier: 'access',
character: `${ level }`,
},
} );
} );
}, [] );

useShortcut(
'core/block-editor/transform-heading-to-paragraph',
( event ) => handleTransformHeadingAndParagraph( event, 0 )
);

[ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => {
//the loop is based off on a constant therefore
//the hook will execute the same way every time
//eslint-disable-next-line react-hooks/rules-of-hooks
useShortcut(
`core/block-editor/transform-paragraph-to-heading-${ level }`,
( event ) => handleTransformHeadingAndParagraph( event, level )
);
} );

return null;
}

export default BlockKeyboardShortcuts;
2 changes: 2 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,5 @@ export const __experimentalRegisterExperimentalCoreBlocks = process.env
.forEach( ( { init } ) => init() );
}
: undefined;

export { privateApis } from './private-apis';
13 changes: 13 additions & 0 deletions packages/block-library/src/private-apis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Internal dependencies
*/
import { default as BlockKeyboardShortcuts } from './block-keyboard-shortcuts';
import { lock } from './lock-unlock';

/**
* @private
*/
export const privateApis = {};
lock( privateApis, {
BlockKeyboardShortcuts,
} );
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,16 @@ export const textFormattingShortcuts = [
description: __( 'Make the selected text inline code.' ),
},
{
keyCombination: { modifier: 'access', character: '0' },
keyCombination: {
modifier: 'access',
character: '0',
},
aliases: [
{
modifier: 'access',
character: '7',
},
],
description: __( 'Convert the current heading to a paragraph.' ),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,10 @@ import {
store as keyboardShortcutsStore,
} from '@wordpress/keyboard-shortcuts';
import { isAppleOS } from '@wordpress/keycodes';
import { useDispatch, useSelect } from '@wordpress/data';
import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { createBlock } from '@wordpress/blocks';

function KeyboardShortcuts( { undo, redo, save } ) {
const { replaceBlocks } = useDispatch( blockEditorStore );
const { getBlockName, getSelectedBlockClientId, getBlockAttributes } =
useSelect( blockEditorStore );

const handleTextLevelShortcut = ( event, level ) => {
event.preventDefault();
const destinationBlockName =
level === 0 ? 'core/paragraph' : 'core/heading';
const currentClientId = getSelectedBlockClientId();
if ( currentClientId === null ) {
return;
}
const blockName = getBlockName( currentClientId );
if ( blockName !== 'core/paragraph' && blockName !== 'core/heading' ) {
return;
}
const attributes = getBlockAttributes( currentClientId );
const textAlign =
blockName === 'core/paragraph' ? 'align' : 'textAlign';
const destinationTextAlign =
destinationBlockName === 'core/paragraph' ? 'align' : 'textAlign';

replaceBlocks(
currentClientId,
createBlock( destinationBlockName, {
level,
content: attributes.content,
...{ [ destinationTextAlign ]: attributes[ textAlign ] },
} )
);
};

useShortcut( 'core/customize-widgets/undo', ( event ) => {
undo();
event.preventDefault();
Expand All @@ -60,21 +26,6 @@ function KeyboardShortcuts( { undo, redo, save } ) {
save();
} );

useShortcut(
'core/customize-widgets/transform-heading-to-paragraph',
( event ) => handleTextLevelShortcut( event, 0 )
);

[ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => {
//the loop is based off on a constant therefore
//the hook will execute the same way every time
//eslint-disable-next-line react-hooks/rules-of-hooks
useShortcut(
`core/customize-widgets/transform-paragraph-to-heading-${ level }`,
( event ) => handleTextLevelShortcut( event, level )
);
} );

return null;
}

Expand Down Expand Up @@ -126,28 +77,6 @@ function KeyboardShortcutsRegister() {
},
} );

registerShortcut( {
name: 'core/customize-widgets/transform-heading-to-paragraph',
category: 'block-library',
description: __( 'Transform heading to paragraph.' ),
keyCombination: {
modifier: 'access',
character: `0`,
},
} );

[ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => {
registerShortcut( {
name: `core/customize-widgets/transform-paragraph-to-heading-${ level }`,
category: 'block-library',
description: __( 'Transform paragraph to heading.' ),
keyCombination: {
modifier: 'access',
character: `${ level }`,
},
} );
} );

return () => {
unregisterShortcut( 'core/customize-widgets/undo' );
unregisterShortcut( 'core/customize-widgets/redo' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@wordpress/block-editor';
import { uploadMedia } from '@wordpress/media-utils';
import { store as preferencesStore } from '@wordpress/preferences';
import { privateApis as blockLibraryPrivateApis } from '@wordpress/block-library';

/**
* Internal dependencies
Expand All @@ -31,6 +32,8 @@ const { ExperimentalBlockCanvas: BlockCanvas } = unlock(
blockEditorPrivateApis
);

const { BlockKeyboardShortcuts } = unlock( blockLibraryPrivateApis );

export default function SidebarBlockEditor( {
blockEditorSettings,
sidebar,
Expand Down Expand Up @@ -99,6 +102,7 @@ export default function SidebarBlockEditor( {
return (
<>
<KeyboardShortcuts.Register />
<BlockKeyboardShortcuts />

<SidebarEditorProvider sidebar={ sidebar } settings={ settings }>
<KeyboardShortcuts
Expand Down
71 changes: 1 addition & 70 deletions packages/edit-post/src/components/keyboard-shortcuts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { useDispatch } from '@wordpress/data';
import {
useShortcut,
store as keyboardShortcutsStore,
} from '@wordpress/keyboard-shortcuts';
import { __ } from '@wordpress/i18n';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { createBlock } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -19,37 +17,6 @@ import { store as editPostStore } from '../../store';
function KeyboardShortcuts() {
const { toggleFeature } = useDispatch( editPostStore );
const { registerShortcut } = useDispatch( keyboardShortcutsStore );
const { replaceBlocks } = useDispatch( blockEditorStore );
const { getBlockName, getSelectedBlockClientId, getBlockAttributes } =
useSelect( blockEditorStore );

const handleTextLevelShortcut = ( event, level ) => {
event.preventDefault();
const destinationBlockName =
level === 0 ? 'core/paragraph' : 'core/heading';
const currentClientId = getSelectedBlockClientId();
if ( currentClientId === null ) {
return;
}
const blockName = getBlockName( currentClientId );
if ( blockName !== 'core/paragraph' && blockName !== 'core/heading' ) {
return;
}
const attributes = getBlockAttributes( currentClientId );
const textAlign =
blockName === 'core/paragraph' ? 'align' : 'textAlign';
const destinationTextAlign =
destinationBlockName === 'core/paragraph' ? 'align' : 'textAlign';

replaceBlocks(
currentClientId,
createBlock( destinationBlockName, {
level,
content: attributes.content,
...{ [ destinationTextAlign ]: attributes[ textAlign ] },
} )
);
};

useEffect( () => {
registerShortcut( {
Expand Down Expand Up @@ -97,48 +64,12 @@ function KeyboardShortcuts() {
},
],
} );

registerShortcut( {
name: 'core/edit-post/transform-heading-to-paragraph',
category: 'block-library',
description: __( 'Transform heading to paragraph.' ),
keyCombination: {
modifier: 'access',
character: `0`,
},
} );

[ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => {
registerShortcut( {
name: `core/edit-post/transform-paragraph-to-heading-${ level }`,
category: 'block-library',
description: __( 'Transform paragraph to heading.' ),
keyCombination: {
modifier: 'access',
character: `${ level }`,
},
} );
} );
}, [] );

useShortcut( 'core/edit-post/toggle-fullscreen', () => {
toggleFeature( 'fullscreenMode' );
} );

useShortcut( 'core/edit-post/transform-heading-to-paragraph', ( event ) =>
handleTextLevelShortcut( event, 0 )
);

[ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => {
//the loop is based off on a constant therefore
//the hook will execute the same way every time
//eslint-disable-next-line react-hooks/rules-of-hooks
useShortcut(
`core/edit-post/transform-paragraph-to-heading-${ level }`,
( event ) => handleTextLevelShortcut( event, level )
);
} );

return null;
}

Expand Down
Loading
Loading