From e5b1ab7eaf0a4f447108dcfaaf2b6615464c7a90 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 16 Jun 2023 11:27:39 +1000 Subject: [PATCH] Rename reusable blocks to patterns & allow user created patterns --- docs/reference-guides/core-blocks.md | 2 +- lib/compat/wordpress-6.3/blocks.php | 91 +++++++++++++++++++ .../inserter/reusable-blocks-tab.js | 6 +- .../src/components/inserter/tabs.js | 6 +- packages/block-library/src/block/block.json | 2 +- packages/block-library/src/block/edit.js | 15 +++ .../src/block/test/edit.native.js | 6 +- .../src/block/test/transforms.native.js | 2 +- .../src/create-reusable-block.js | 7 +- .../block-editor-keyboard-shortcuts.test.js | 2 +- .../components/sidebar/post-status/index.js | 3 +- packages/edit-post/src/plugins/index.js | 2 +- .../index.js | 2 +- packages/editor/src/components/index.js | 1 + .../src/components/post-sync-status/index.js | 55 +++++++++++ .../components/post-sync-status/style.scss | 16 ++++ packages/editor/src/style.scss | 1 + .../reusable-block-convert-button.js | 60 +++++++++--- .../reusable-blocks-manage-button.js | 2 +- packages/reusable-blocks/src/store/actions.js | 25 ++++- 20 files changed, 271 insertions(+), 35 deletions(-) create mode 100644 packages/editor/src/components/post-sync-status/index.js create mode 100644 packages/editor/src/components/post-sync-status/style.scss diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index dec943d4bb790..eb634c5ac7647 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -36,7 +36,7 @@ Add a user’s avatar. ([Source](https://github.com/WordPress/gutenberg/tree/tru - **Supports:** align, color (~~background~~, ~~text~~), spacing (margin, padding), ~~alignWide~~, ~~html~~ - **Attributes:** isLink, linkTarget, size, userId -## Reusable block +## Pattern Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/block)) diff --git a/lib/compat/wordpress-6.3/blocks.php b/lib/compat/wordpress-6.3/blocks.php index 5f017997f52b5..0e123d3e82c83 100644 --- a/lib/compat/wordpress-6.3/blocks.php +++ b/lib/compat/wordpress-6.3/blocks.php @@ -26,3 +26,94 @@ function gutenberg_add_selectors_property_to_block_type_settings( $settings, $me return $settings; } add_filter( 'block_type_metadata_settings', 'gutenberg_add_selectors_property_to_block_type_settings', 10, 2 ); + +/** + * Adds custom fields support to the wp_block post type so an unsynced option can be added and renames + * from Reusable block to Pattern. + * + * Note: This should be removed when the minimum required WP version is >= 6.3. + * + * @see https://github.com/WordPress/gutenberg/pull/51144 + * + * @param array $args Register post type args. + * @param string $post_type The post type string. + * + * @return array Register post type args. + */ +function gutenberg_add_custom_fields_to_wp_block( $args, $post_type ) { + if ( 'wp_block' === $post_type ) { + $args['labels']['name'] = _x( 'Patterns', 'post type general name' ); + $args['labels']['singular_name'] = _x( 'Pattern', 'post type singular name' ); + $args['labels']['add_new_item'] = __( 'Add new Pattern' ); + $args['labels']['new_item'] = __( 'New Pattern' ); + $args['labels']['edit_item'] = __( 'Edit Pattern' ); + $args['labels']['view_item'] = __( 'View Pattern' ); + $args['labels']['all_items'] = __( 'All Patterns' ); + $args['labels']['search_items'] = __( 'Search Patterns' ); + $args['labels']['not_found'] = __( 'No Patterns found.' ); + $args['labels']['not_found_in_trash'] = __( 'No Patterns found in Trash.' ); + $args['labels']['filter_items_list'] = __( 'Filter Patterns list' ); + $args['labels']['items_list_navigation'] = __( 'Patterns list navigation' ); + $args['labels']['items_list'] = __( 'Patterns list' ); + $args['labels']['item_published'] = __( 'Pattern published.' ); + $args['labels']['item_published_privately'] = __( 'Pattern published privately.' ); + $args['labels']['item_reverted_to_draft'] = __( 'Pattern reverted to draft.' ); + $args['labels']['item_scheduled'] = __( 'Pattern scheduled.' ); + $args['labels']['item_updated'] = __( 'Pattern updated.' ); + array_push( $args['supports'], 'custom-fields' ); + } + + return $args; +} +add_filter( 'register_post_type_args', 'gutenberg_add_custom_fields_to_wp_block', 10, 2 ); + +/** + * Adds wp_block_sync_status meta fields to the wp_block post type so an unsynced option can be added. + * + * Note: This should be removed when the minimum required WP version is >= 6.3. + * + * @see https://github.com/WordPress/gutenberg/pull/51144 + * + * @return void + */ +function gutenberg_wp_block_register_post_meta() { + $post_type = 'wp_block'; + register_post_meta( + $post_type, + 'wp_block', + array( + 'auth_callback' => function() { + return current_user_can( 'edit_posts' ); + }, + 'sanitize_callback' => 'gutenberg_wp_block_sanitize_post_meta', + 'single' => true, + 'type' => 'object', + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + 'sync_status' => array( + 'type' => 'string', + ), + ), + ), + ), + ) + ); +} +/** + * Sanitizes the array of wp_block post meta categories array. + * + * Note: This should be removed when the minimum required WP version is >= 6.3. + * + * @see https://github.com/WordPress/gutenberg/pull/51144 + * + * @param array $meta_value Array of values to sanitize. + * + * @return array Sanitized array of values. + */ +function gutenberg_wp_block_sanitize_post_meta( $meta_value ) { + $meta_value['sync_status'] = sanitize_text_field( $meta_value['sync_status'] ); + return $meta_value; +} +add_action( 'init', 'gutenberg_wp_block_register_post_meta' ); diff --git a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js index 9505dd77f3b94..0de66c83a2cb6 100644 --- a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js +++ b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js @@ -29,12 +29,12 @@ function ReusableBlocksList( { onHover, onInsert, rootClientId } ) { } return ( - + ); @@ -67,7 +67,7 @@ export function ReusableBlocksTab( { rootClientId, onInsert, onHover } ) { post_type: 'wp_block', } ) } > - { __( 'Manage Reusable blocks' ) } + { __( 'Manage Synced Patterns' ) } diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index 6f8377892059b..f11ce9d31ab5d 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -13,13 +13,13 @@ const blocksTab = { }; const patternsTab = { name: 'patterns', - /* translators: Patterns tab title in the block inserter. */ + /* translators: Theme and Directory Patterns tab title in the block inserter. */ title: __( 'Patterns' ), }; const reusableBlocksTab = { name: 'reusable', - /* translators: Reusable blocks tab title in the block inserter. */ - title: __( 'Reusable' ), + /* translators: Locally created Patterns tab title in the block inserter. */ + title: __( 'Synced Patterns' ), icon: reusableBlockIcon, }; const mediaTab = { diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json index 78fed058a5bd1..5846e7ead0c9b 100644 --- a/packages/block-library/src/block/block.json +++ b/packages/block-library/src/block/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/block", - "title": "Reusable block", + "title": "Pattern", "category": "reusable", "description": "Create and save content to reuse across your site. Update the block, and the changes apply everywhere it’s used.", "textdomain": "default", diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index fcba45450ea5e..7fc2faf048472 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -29,6 +29,7 @@ import { } from '@wordpress/block-editor'; import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; import { ungroup } from '@wordpress/icons'; +import { cloneBlock } from '@wordpress/blocks'; export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { const hasAlreadyRendered = useHasRecursion( ref ); @@ -54,11 +55,25 @@ export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { const { __experimentalConvertBlockToStatic: convertBlockToStatic } = useDispatch( reusableBlocksStore ); + const { replaceBlocks } = useDispatch( blockEditorStore ); + const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', 'wp_block', { id: ref } ); + + if ( + hasResolved && + record?.meta?.wp_block?.sync_status === 'unsynced' && + blocks.length > 0 + ) { + replaceBlocks( + clientId, + blocks.map( ( block ) => cloneBlock( block ) ) + ); + } + const [ title, setTitle ] = useEntityProp( 'postType', 'wp_block', diff --git a/packages/block-library/src/block/test/edit.native.js b/packages/block-library/src/block/test/edit.native.js index 4652f8ba20f38..ae9d3d7c03e3e 100644 --- a/packages/block-library/src/block/test/edit.native.js +++ b/packages/block-library/src/block/test/edit.native.js @@ -114,7 +114,7 @@ describe( 'Reusable block', () => { // Get the reusable block. const [ reusableBlock ] = await screen.findAllByLabelText( - /Reusable block Block\. Row 1/ + /Pattern Block\. Row 1/ ); expect( reusableBlock ).toBeDefined(); @@ -131,7 +131,7 @@ describe( 'Reusable block', () => { } ); const [ reusableBlock ] = await screen.findAllByLabelText( - /Reusable block Block\. Row 1/ + /Pattern Block\. Row 1/ ); const blockDeleted = within( reusableBlock ).getByText( @@ -164,7 +164,7 @@ describe( 'Reusable block', () => { } ); const [ reusableBlock ] = await screen.findByLabelText( - /Reusable block Block\. Row 1/ + /Pattern Block\. Row 1/ ); const innerBlockListWrapper = await within( diff --git a/packages/block-library/src/block/test/transforms.native.js b/packages/block-library/src/block/test/transforms.native.js index 9771b40743a42..95104ac613399 100644 --- a/packages/block-library/src/block/test/transforms.native.js +++ b/packages/block-library/src/block/test/transforms.native.js @@ -9,7 +9,7 @@ import { getBlockTransformOptions, } from 'test/helpers'; -const block = 'Reusable block'; +const block = 'Pattern'; const initialHtml = ` `; diff --git a/packages/e2e-test-utils/src/create-reusable-block.js b/packages/e2e-test-utils/src/create-reusable-block.js index ec35e07390847..651e5b639e689 100644 --- a/packages/e2e-test-utils/src/create-reusable-block.js +++ b/packages/e2e-test-utils/src/create-reusable-block.js @@ -15,17 +15,22 @@ import { canvas } from './canvas'; export const createReusableBlock = async ( content, title ) => { const reusableBlockNameInputSelector = '.reusable-blocks-menu-items__convert-modal .components-text-control__input'; + const syncToggleSelector = '.components-form-toggle__input'; // Insert a paragraph block await insertBlock( 'Paragraph' ); await page.keyboard.type( content ); await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Create Reusable block' ); + await clickMenuItem( 'Create a Pattern' ); const nameInput = await page.waitForSelector( reusableBlockNameInputSelector ); await nameInput.click(); await page.keyboard.type( title ); + const syncToggle = await page.waitForSelector( + `${ reusableBlockNameInputSelector } ${ syncToggleSelector }` + ); + syncToggle.click(); await page.keyboard.press( 'Enter' ); // Wait for creation to finish diff --git a/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js b/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js index f1e6be7b816ab..97248c472e4ac 100644 --- a/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js +++ b/packages/e2e-tests/specs/editor/various/block-editor-keyboard-shortcuts.test.js @@ -90,7 +90,7 @@ describe( 'block editor keyboard shortcuts', () => { } ); it( 'should prevent deleting multiple selected blocks from inputs', async () => { await clickBlockToolbarButton( 'Options' ); - await clickMenuItem( 'Create Reusable block' ); + await clickMenuItem( 'Create a Pattern' ); const reusableBlockNameInputSelector = '.reusable-blocks-menu-items__convert-modal .components-text-control__input'; const nameInput = await page.waitForSelector( diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 8304bb8b4f6ea..5cc9b70bf9ac3 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -8,7 +8,7 @@ import { } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose, ifCondition } from '@wordpress/compose'; -import { PostSwitchToDraftButton } from '@wordpress/editor'; +import { PostSwitchToDraftButton, PostSyncStatus } from '@wordpress/editor'; /** * Internal dependencies @@ -51,6 +51,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { + { fills } - { __( 'Manage Reusable blocks' ) } + { __( 'Manage Patterns' ) } { + const { getEditedPostAttribute } = select( editorStore ); + return { + meta: getEditedPostAttribute( 'meta' ), + postType: getEditedPostAttribute( 'type' ), + }; + }, [] ); + if ( postType !== 'wp_block' ) { + return null; + } + const onUpdateSync = ( syncStatus ) => + editPost( { + meta: { + ...meta, + wp_block: + syncStatus === 'unsynced' + ? { sync_status: syncStatus } + : null, + }, + } ); + const syncStatus = meta?.wp_block?.sync_status; + const isFullySynced = ! syncStatus; + + return ( + + { __( 'Syncing' ) } + { + onUpdateSync( + syncStatus === 'unsynced' ? 'fully' : 'unsynced' + ); + } } + /> + + ); +} diff --git a/packages/editor/src/components/post-sync-status/style.scss b/packages/editor/src/components/post-sync-status/style.scss new file mode 100644 index 0000000000000..385577b3334d8 --- /dev/null +++ b/packages/editor/src/components/post-sync-status/style.scss @@ -0,0 +1,16 @@ +.edit-post-sync-status { + width: 100%; + position: relative; + justify-content: flex-start; + + > span { + display: block; + width: 45%; + flex-shrink: 0; + } + + .components-base-control { + // Match padding on tertiary buttons for alignment. + padding-left: $grid-unit-15 * 0.5; + } +} diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 9d035ec4d654a..dbffbbef4d521 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -11,6 +11,7 @@ @import "./components/post-publish-button/style.scss"; @import "./components/post-publish-panel/style.scss"; @import "./components/post-saved-state/style.scss"; +@import "./components/post-sync-status/style.scss"; @import "./components/post-taxonomies/style.scss"; @import "./components/post-text-editor/style.scss"; @import "./components/post-url/style.scss"; diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js index 18824b892544a..a723d784d89b6 100644 --- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js +++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js @@ -14,10 +14,11 @@ import { TextControl, __experimentalHStack as HStack, __experimentalVStack as VStack, + ToggleControl, } from '@wordpress/components'; import { symbol } from '@wordpress/icons'; import { useDispatch, useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { store as coreStore } from '@wordpress/core-data'; @@ -38,6 +39,7 @@ export default function ReusableBlockConvertButton( { clientIds, rootClientId, } ) { + const [ syncType, setSyncType ] = useState( 'unsynced' ); const [ isModalOpen, setIsModalOpen ] = useState( false ); const [ title, setTitle ] = useState( '' ); const canConvert = useSelect( @@ -77,7 +79,7 @@ export default function ReusableBlockConvertButton( { return _canConvert; }, - [ clientIds ] + [ clientIds, rootClientId ] ); const { __experimentalConvertBlocksToReusable: convertBlocksToReusable } = @@ -88,17 +90,36 @@ export default function ReusableBlockConvertButton( { const onConvert = useCallback( async function ( reusableBlockTitle ) { try { - await convertBlocksToReusable( clientIds, reusableBlockTitle ); - createSuccessNotice( __( 'Reusable block created.' ), { - type: 'snackbar', - } ); + await convertBlocksToReusable( + clientIds, + reusableBlockTitle, + syncType + ); + createSuccessNotice( + sprintf( + // translators: %s: The sync status of the block that is created. + __( '%s created.' ), + syncType === 'fully' + ? __( 'Synced Pattern' ) + : __( 'Unsynced Pattern' ) + ), + { + type: 'snackbar', + } + ); } catch ( error ) { createErrorNotice( error.message, { type: 'snackbar', } ); } }, - [ clientIds ] + [ + convertBlocksToReusable, + clientIds, + syncType, + createSuccessNotice, + createErrorNotice, + ] ); if ( ! canConvert ) { @@ -111,15 +132,13 @@ export default function ReusableBlockConvertButton( { <> { - setIsModalOpen( true ); - } } + onClick={ () => setIsModalOpen( true ) } > - { __( 'Create Reusable block' ) } + { __( 'Create a Pattern' ) } { isModalOpen && ( { setIsModalOpen( false ); setTitle( '' ); @@ -142,6 +161,23 @@ export default function ReusableBlockConvertButton( { value={ title } onChange={ setTitle } /> + + { + setSyncType( + syncType === 'fully' + ? 'unsynced' + : 'fully' + ); + } } + />