Skip to content

Commit

Permalink
Reusable blocks: Rename to 'Patterns' and add option to also add a no…
Browse files Browse the repository at this point in the history
…n-synced Pattern (WordPress#51144)

---------

Co-authored-by: Saxon Fletcher <saxonafletcher@gmail.com>
Co-authored-by: Daniel Richards <daniel.richards@automattic.com>
  • Loading branch information
3 people authored and sethrubenstein committed Jul 13, 2023
1 parent a9fcef3 commit d53a625
Show file tree
Hide file tree
Showing 29 changed files with 389 additions and 81 deletions.
4 changes: 2 additions & 2 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -473,7 +473,7 @@ Start with the basic building block of all narrative. ([Source](https://github.c
- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~
- **Attributes:** align, content, direction, dropCap, placeholder

## Pattern
## Pattern placeholder

Show a block pattern. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/pattern))

Expand Down
1 change: 1 addition & 0 deletions docs/reference-guides/data/data-core-block-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ _Parameters_
- _state_ `Object`: Editor state.
- _rootClientId_ `?string`: Optional root client ID of block list.
- _syncStatus_ `?string`: Optional sync status to filter pattern blocks by.
_Returns_
Expand Down
110 changes: 110 additions & 0 deletions lib/compat/wordpress-6.3/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,113 @@ 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 );

/**
* Renames Reusable block CPT 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_rename_reusable_block_cpt_to_pattern( $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.' );
}

return $args;
}

add_filter( 'register_post_type_args', 'gutenberg_rename_reusable_block_cpt_to_pattern', 10, 2 );

/**
* Adds custom fields support 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
*
* @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 ) {
array_push( $args['supports'], 'custom-fields' );
}

return $args;
}
add_filter( 'register_post_type_args', 'gutenberg_add_custom_fields_to_wp_block', 10, 2 );

/**
* Adds 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,
'sync_status',
array(
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
'sanitize_callback' => 'gutenberg_wp_block_sanitize_post_meta',
'single' => true,
'type' => 'string',
'show_in_rest' => array(
'schema' => array(
'type' => 'string',
'properties' => array(
'sync_status' => array(
'type' => 'string',
),
),
),
),
)
);
}
/**
* Sanitizes the array of wp_block post meta sync_status string.
*
* 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 String to sanitize.
*
* @return array Sanitized string.
*/
function gutenberg_wp_block_sanitize_post_meta( $meta_value ) {
return sanitize_text_field( $meta_value );
}
add_action( 'init', 'gutenberg_wp_block_register_post_meta' );
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Button,
} from '@wordpress/components';
import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';
import { parse } from '@wordpress/blocks';
import { focus } from '@wordpress/dom';

/**
Expand All @@ -27,6 +28,7 @@ import usePatternsState from './hooks/use-patterns-state';
import BlockPatternList from '../block-patterns-list';
import PatternsExplorerModal from './block-patterns-explorer/explorer';
import MobileTabNavigation from './mobile-tab-navigation';
import useBlockTypesState from './hooks/use-block-types-state';

const noop = () => {};

Expand All @@ -49,6 +51,18 @@ function usePatternsCategories( rootClientId ) {
rootClientId
);

const [ unsyncedPatterns ] = useBlockTypesState(
rootClientId,
undefined,
'unsynced'
);

const filteredUnsyncedPatterns = useMemo( () => {
return unsyncedPatterns.filter(
( { category: unsyncedPatternCategory } ) =>
unsyncedPatternCategory === 'reusable'
);
}, [ unsyncedPatterns ] );
const hasRegisteredCategory = useCallback(
( pattern ) => {
if ( ! pattern.categories || ! pattern.categories.length ) {
Expand Down Expand Up @@ -93,9 +107,20 @@ function usePatternsCategories( rootClientId ) {
label: _x( 'Uncategorized' ),
} );
}
if ( filteredUnsyncedPatterns.length > 0 ) {
categories.push( {
name: 'reusable',
label: _x( 'Custom patterns' ),
} );
}

return categories;
}, [ allPatterns, allCategories ] );
}, [
allCategories,
allPatterns,
filteredUnsyncedPatterns.length,
hasRegisteredCategory,
] );

return populatedCategories;
}
Expand Down Expand Up @@ -144,6 +169,24 @@ export function BlockPatternsCategoryPanel( {
onInsert,
rootClientId
);
const [ unsyncedPatterns ] = useBlockTypesState(
rootClientId,
onInsert,
'unsynced'
);
const filteredUnsyncedPatterns = useMemo( () => {
return unsyncedPatterns
.filter(
( { category: unsyncedPatternCategory } ) =>
unsyncedPatternCategory === 'reusable'
)
.map( ( syncedPattern ) => ( {
...syncedPattern,
blocks: parse( syncedPattern.content, {
__unstableSkipMigrationLogs: true,
} ),
} ) );
}, [ unsyncedPatterns ] );

const availableCategories = usePatternsCategories( rootClientId );
const currentCategoryPatterns = useMemo(
Expand All @@ -167,13 +210,19 @@ export function BlockPatternsCategoryPanel( {
} ),
[ allPatterns, category ]
);

const currentShownPatterns = useAsyncList( currentCategoryPatterns );
const patterns =
category.name === 'reusable'
? filteredUnsyncedPatterns
: currentCategoryPatterns;
const currentShownPatterns = useAsyncList( patterns );

// Hide block pattern preview on unmount.
useEffect( () => () => onHover( null ), [] );

if ( ! currentCategoryPatterns.length ) {
if (
! currentCategoryPatterns.length &&
! filteredUnsyncedPatterns.length
) {
return null;
}

Expand All @@ -185,7 +234,7 @@ export function BlockPatternsCategoryPanel( {
<p>{ category.description }</p>
<BlockPatternList
shownPatterns={ currentShownPatterns }
blockPatterns={ currentCategoryPatterns }
blockPatterns={ patterns }
onClickPattern={ onClick }
onHover={ onHover }
label={ category.label }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import { store as blockEditorStore } from '../../../store';
*
* @param {string=} rootClientId Insertion's root client ID.
* @param {Function} onInsert function called when inserter a list of blocks.
* @param {?string} syncStatus Optional sync status to filter pattern blocks by.
* @return {Array} Returns the block types state. (block types, categories, collections, onSelect handler)
*/
const useBlockTypesState = ( rootClientId, onInsert ) => {
const useBlockTypesState = ( rootClientId, onInsert, syncStatus ) => {
const { categories, collections, items } = useSelect(
( select ) => {
const { getInserterItems } = select( blockEditorStore );
Expand All @@ -30,10 +31,10 @@ const useBlockTypesState = ( rootClientId, onInsert ) => {
return {
categories: getCategories(),
collections: getCollections(),
items: getInserterItems( rootClientId ),
items: getInserterItems( rootClientId, syncStatus ),
};
},
[ rootClientId ]
[ rootClientId, syncStatus ]
);

const onSelectItem = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ function ReusableBlocksList( { onHover, onInsert, rootClientId } ) {
}

return (
<InserterPanel title={ __( 'Reusable blocks' ) }>
<InserterPanel title={ __( 'Synced patterns' ) }>
<BlockTypesList
items={ filteredItems }
onSelect={ onSelectItem }
onHover={ onHover }
label={ __( 'Reusable blocks' ) }
label={ __( 'Synced patterns' ) }
/>
</InserterPanel>
);
Expand Down Expand Up @@ -67,7 +67,7 @@ export function ReusableBlocksTab( { rootClientId, onInsert, onHover } ) {
post_type: 'wp_block',
} ) }
>
{ __( 'Manage Reusable blocks' ) }
{ __( 'Manage custom patterns' ) }
</Button>
</div>
</>
Expand Down
6 changes: 3 additions & 3 deletions packages/block-editor/src/components/inserter/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
13 changes: 11 additions & 2 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,7 @@ const buildBlockTypeItem =
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
* @param {?string} syncStatus Optional sync status to filter pattern blocks by.
*
* @return {WPEditorInserterItem[]} Items that appear in inserter.
*
Expand All @@ -1961,7 +1962,7 @@ const buildBlockTypeItem =
* @property {number} frecency Heuristic that combines frequency and recency.
*/
export const getInserterItems = createSelector(
( state, rootClientId = null ) => {
( state, rootClientId = null, syncStatus ) => {
const buildBlockTypeInserterItem = buildBlockTypeItem( state, {
buildScope: 'inserter',
} );
Expand Down Expand Up @@ -2026,6 +2027,7 @@ export const getInserterItems = createSelector(
isDisabled: false,
utility: 1, // Deprecated.
frecency,
content: reusableBlock.content.raw,
};
};

Expand All @@ -2040,7 +2042,14 @@ export const getInserterItems = createSelector(
'core/block',
rootClientId
)
? getReusableBlocks( state ).map( buildReusableBlockInserterItem )
? getReusableBlocks( state )
.filter(
( reusableBlock ) =>
syncStatus === reusableBlock.meta?.sync_status ||
( ! syncStatus &&
reusableBlock.meta?.sync_status === '' )
)
.map( buildReusableBlockInserterItem )
: [];

const items = blockTypeInserterItems.reduce( ( accumulator, item ) => {
Expand Down
Loading

0 comments on commit d53a625

Please sign in to comment.