diff --git a/packages/js/product-editor/changelog/add-keyboard-shortcuts b/packages/js/product-editor/changelog/add-keyboard-shortcuts new file mode 100644 index 00000000000..e09c109e440 --- /dev/null +++ b/packages/js/product-editor/changelog/add-keyboard-shortcuts @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Allow saving a product as a draft when hitting ctrl/cmd + S diff --git a/packages/js/product-editor/src/components/block-editor/block-editor.tsx b/packages/js/product-editor/src/components/block-editor/block-editor.tsx index ba72d28406f..8b1ae44266e 100644 --- a/packages/js/product-editor/src/components/block-editor/block-editor.tsx +++ b/packages/js/product-editor/src/components/block-editor/block-editor.tsx @@ -14,6 +14,7 @@ import { uploadMedia } from '@wordpress/media-utils'; import { PluginArea } from '@wordpress/plugins'; import { __ } from '@wordpress/i18n'; import { useLayoutTemplate } from '@woocommerce/block-templates'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { Product } from '@woocommerce/data'; import { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -94,6 +95,23 @@ export function BlockEditor( { return () => window.removeEventListener( 'scroll', wpPinMenuEvent ); }, [] ); + // @ts-expect-error Type definitions are missing + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); + + useEffect( () => { + if ( registerShortcut ) { + registerShortcut( { + name: 'core/editor/save', + category: 'global', + description: __( 'Save your changes.', 'woocommerce' ), + keyCombination: { + modifier: 'primary', + character: 's', + }, + } ); + } + }, [ registerShortcut ] ); + const [ settingsGlobal, setSettingsGlobal ] = useState< Partial< ProductEditorSettings > | undefined >( undefined ); diff --git a/packages/js/product-editor/src/components/header/hooks/use-publish/use-publish.tsx b/packages/js/product-editor/src/components/header/hooks/use-publish/use-publish.tsx index a2b0a73f41f..79ad697da6b 100644 --- a/packages/js/product-editor/src/components/header/hooks/use-publish/use-publish.tsx +++ b/packages/js/product-editor/src/components/header/hooks/use-publish/use-publish.tsx @@ -6,6 +6,7 @@ import { Button } from '@wordpress/components'; import { useEntityProp } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import type { Product } from '@woocommerce/data'; +import { useShortcut } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -41,6 +42,9 @@ export function usePublish< T = Product >( { const isDisabled = prevStatus !== 'draft' && ( disabled || isBusy || ! isDirty ); + const handlePublish = () => + publish().then( onPublishSuccess ).catch( onPublishError ); + function handleClick( event: MouseEvent< HTMLButtonElement > ) { if ( isDisabled ) { event.preventDefault?.(); @@ -51,7 +55,7 @@ export function usePublish< T = Product >( { onClick( event ); } - publish().then( onPublishSuccess ).catch( onPublishError ); + handlePublish(); } function getButtonText() { @@ -69,6 +73,16 @@ export function usePublish< T = Product >( { return __( 'Publish', 'woocommerce' ); } + useShortcut( 'core/editor/save', ( event ) => { + event.preventDefault(); + if ( + ! isDisabled && + ( prevStatus === 'publish' || prevStatus === 'future' ) + ) { + handlePublish(); + } + } ); + return { children: getButtonText(), ...props, diff --git a/packages/js/product-editor/src/components/header/hooks/use-save-draft/use-save-draft.tsx b/packages/js/product-editor/src/components/header/hooks/use-save-draft/use-save-draft.tsx index 83562841292..e5c3984b5c1 100644 --- a/packages/js/product-editor/src/components/header/hooks/use-save-draft/use-save-draft.tsx +++ b/packages/js/product-editor/src/components/header/hooks/use-save-draft/use-save-draft.tsx @@ -9,6 +9,7 @@ import { __ } from '@wordpress/i18n'; import { check } from '@wordpress/icons'; import { createElement, Fragment } from '@wordpress/element'; import { MouseEvent, ReactNode } from 'react'; +import { useShortcut } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -69,22 +70,13 @@ export function useSaveDraft( { // @ts-expect-error There are no types for this. const { editEntityRecord, saveEditedEntityRecord } = useDispatch( 'core' ); - async function handleClick( event: MouseEvent< HTMLButtonElement > ) { - if ( ariaDisabled ) { - return event.preventDefault(); - } - - if ( onClick ) { - onClick( event ); - } - + async function saveDraft() { try { await validate( { status: 'draft' } ); await editEntityRecord( 'postType', productType, productId, { status: 'draft', } ); - // @ts-expect-error There are no types for this. const publishedProduct = await saveEditedEntityRecord< Product >( 'postType', productType, @@ -104,6 +96,17 @@ export function useSaveDraft( { } } + async function handleClick( event: MouseEvent< HTMLButtonElement > ) { + if ( ariaDisabled ) { + return event.preventDefault(); + } + + if ( onClick ) { + onClick( event ); + } + await saveDraft(); + } + let children: ReactNode; if ( productStatus === 'publish' ) { children = __( 'Switch to draft', 'woocommerce' ); @@ -118,6 +121,16 @@ export function useSaveDraft( { ); } + useShortcut( 'core/editor/save', ( event ) => { + event.preventDefault(); + if ( + ! ariaDisabled && + ( productStatus === 'draft' || productStatus === 'auto-draft' ) + ) { + saveDraft(); + } + } ); + return { children, ...props, diff --git a/packages/js/product-editor/typings/index.d.ts b/packages/js/product-editor/typings/index.d.ts index 67de76ccd18..24fa580c455 100644 --- a/packages/js/product-editor/typings/index.d.ts +++ b/packages/js/product-editor/typings/index.d.ts @@ -25,3 +25,7 @@ declare module '@wordpress/core-data' { id: number | string ): { record: T, editedRecord: T }; } +declare module '@wordpress/keyboard-shortcuts' { + function useShortcut(name: string, callback: (event: KeyboardEvent) => void): void; + const store; +}