Skip to content

Commit

Permalink
Template Parts: Add an option to import widgets from the sidebars (#4…
Browse files Browse the repository at this point in the history
…5509)

* Template Part: Introduce an option to import widgets
* Only display active sidebars with widgets as options
* Use the sidebar name as the new template part title
* Support legacy widgets
* Prefix imported template parts to avoid name collision
* Add missing docblock
* Update labels
* Move settings into the advanced inspector controls panel
* Update API response for sidebars and mark them as 'inactive'
* A minor design adjustments
* Fix the rendering order bug
* Transform legacy widgets before importing
* Avoid hardcoding names of the blocks with wildcard transformations
* Skip 'wp_inactive_widgets' widget area
* Use 'HStack'
* Allow overriding Legacy Widget block settings during registration
* Override only supports settings
* Improve spacing
* Add the legacy widget to the post editor

Co-authored-by: Robert Anderson <robert@noisysocks.com>
  • Loading branch information
Mamaduka and noisysocks committed Dec 15, 2022
1 parent 489d48a commit 1e6538b
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 2 deletions.
15 changes: 15 additions & 0 deletions lib/compat/wordpress-6.2/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,18 @@ function gutenberg_register_global_styles_endpoints() {
$editor_settings->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' );

/**
* Updates REST API response for the sidebars and marks them as 'inactive'.
*
* Note: This can be a part of the `prepare_item_for_response` in `class-wp-rest-sidebars-controller.php`.
*
* @param WP_REST_Response $response The sidebar response object.
* @return WP_REST_Response $response Updated response object.
*/
function gutenberg_modify_rest_sidebars_response( $response ) {
$response->data['status'] = wp_is_block_theme() ? 'inactive' : 'active';

return $response;
}
add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' );
23 changes: 23 additions & 0 deletions lib/compat/wordpress-6.2/theme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* Theme overrides for WP 6.2.
*
* @package gutenberg
*/

/**
* Store legacy sidebars for later use by block themes.
*
* Note: This can be a part of the `switch_theme` method in `wp-includes/theme.php`.
*
* @param string $new_name Name of the new theme.
* @param WP_Theme $new_theme WP_Theme instance of the new theme.
*/
function gutenberg_set_legacy_sidebars( $new_name, $new_theme ) {
global $wp_registered_sidebars;

if ( $new_theme->is_block_theme() ) {
set_theme_mod( 'wp_legacy_sidebars', $wp_registered_sidebars );
}
}
add_action( 'switch_theme', 'gutenberg_set_legacy_sidebars', 10, 2 );
32 changes: 32 additions & 0 deletions lib/compat/wordpress-6.2/widgets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* Core Widget APIs for WP 6.2.
*
* @package gutenberg
*/

if ( ! function_exists( '_wp_block_theme_stub_sidebars' ) ) {
/**
* Register the previous theme's sidebars for the block themes.
*
* @return void
*/
function _wp_block_theme_stub_sidebars() {
global $wp_registered_sidebars;

if ( ! wp_is_block_theme() ) {
return;
}

$legacy_sidebars = get_theme_mod( 'wp_legacy_sidebars' );
if ( empty( $legacy_sidebars ) ) {
return;
}

// Don't use `register_sidebar` since it will enable the `widgets` support for a theme.
foreach ( $legacy_sidebars as $sidebar ) {
$wp_registered_sidebars[ $sidebar['id'] ] = $sidebar;
}
}
add_action( 'widgets_init', '_wp_block_theme_stub_sidebars' );
}
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.2/edit-form-blocks.php';
require __DIR__ . '/compat/wordpress-6.2/site-editor.php';
require __DIR__ . '/compat/wordpress-6.2/block-editor-settings.php';
require __DIR__ . '/compat/wordpress-6.2/theme.php';
require __DIR__ . '/compat/wordpress-6.2/widgets.php';

// Experimental features.
remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WP 6.0's stopgap handler for Webfonts API.
Expand Down
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.

12 changes: 12 additions & 0 deletions packages/block-library/src/template-part/edit/advanced-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import { sprintf, __ } from '@wordpress/i18n';
import { InspectorControls } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { TemplatePartImportControls } from './import-controls';

export function TemplatePartAdvancedControls( {
tagName,
setAttributes,
isEntityAvailable,
templatePartId,
defaultWrapper,
hasInnerBlocks,
} ) {
const [ area, setArea ] = useEntityProp(
'postType',
Expand Down Expand Up @@ -87,6 +93,12 @@ export function TemplatePartAdvancedControls( {
value={ tagName || '' }
onChange={ ( value ) => setAttributes( { tagName: value } ) }
/>
{ ! hasInnerBlocks && (
<TemplatePartImportControls
area={ area }
setAttributes={ setAttributes }
/>
) }
</InspectorControls>
);
}
180 changes: 180 additions & 0 deletions packages/block-library/src/template-part/edit/import-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { useMemo, useState } from '@wordpress/element';
import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
import {
Button,
FlexBlock,
FlexItem,
SelectControl,
__experimentalHStack as HStack,
__experimentalSpacer as Spacer,
} from '@wordpress/components';
import {
switchToBlockType,
getPossibleBlockTransformations,
} from '@wordpress/blocks';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
*/
import { useCreateTemplatePartFromBlocks } from './utils/hooks';
import { transformWidgetToBlock } from './utils/transformers';

export function TemplatePartImportControls( { area, setAttributes } ) {
const [ selectedSidebar, setSelectedSidebar ] = useState( '' );
const [ isBusy, setIsBusy ] = useState( false );

const registry = useRegistry();
const sidebars = useSelect( ( select ) => {
return select( coreStore ).getSidebars( {
per_page: -1,
_fields: 'id,name,description,status,widgets',
} );
}, [] );
const { createErrorNotice } = useDispatch( noticesStore );

const createFromBlocks = useCreateTemplatePartFromBlocks(
area,
setAttributes
);

const options = useMemo( () => {
const sidebarOptions = ( sidebars ?? [] )
.filter(
( widgetArea ) =>
widgetArea.id !== 'wp_inactive_widgets' &&
widgetArea.widgets.length > 0
)
.map( ( widgetArea ) => {
return {
value: widgetArea.id,
label: widgetArea.name,
};
} );

if ( ! sidebarOptions.length ) {
return [];
}

return [
{ value: '', label: __( 'Select widget area' ) },
...sidebarOptions,
];
}, [ sidebars ] );

async function createFromWidgets( event ) {
event.preventDefault();

if ( isBusy || ! selectedSidebar ) {
return;
}

setIsBusy( true );

const sidebar = options.find(
( { value } ) => value === selectedSidebar
);
const { getWidgets } = registry.resolveSelect( coreStore );

// The widgets API always returns a successful response.
const widgets = await getWidgets( {
sidebar: sidebar.value,
_embed: 'about',
} );

const skippedWidgets = new Set();
const blocks = widgets.flatMap( ( widget ) => {
const block = transformWidgetToBlock( widget );

if ( block.name !== 'core/legacy-widget' ) {
return block;
}

const transforms = getPossibleBlockTransformations( [
block,
] ).filter( ( item ) => {
// The block without any transformations can't be a wildcard.
if ( ! item.transforms ) {
return true;
}

const hasWildCardFrom = item.transforms?.from?.find(
( from ) => from.blocks && from.blocks.includes( '*' )
);
const hasWildCardTo = item.transforms?.to?.find(
( to ) => to.blocks && to.blocks.includes( '*' )
);

return ! hasWildCardFrom && ! hasWildCardTo;
} );

// Skip the block if we have no matching transformations.
if ( ! transforms.length ) {
skippedWidgets.add( widget.id_base );
return [];
}

// Try transforming the Legacy Widget into a first matching block.
return switchToBlockType( block, transforms[ 0 ].name );
} );

await createFromBlocks(
blocks,
/* translators: %s: name of the widget area */
sprintf( __( 'Widget area: %s' ), sidebar.label )
);

if ( skippedWidgets.size ) {
createErrorNotice(
sprintf(
/* translators: %s: the list of widgets */
__( 'Unable to import the following widgets: %s.' ),
Array.from( skippedWidgets ).join( ', ' )
),
{
type: 'snackbar',
}
);
}

setIsBusy( false );
}

return (
<Spacer marginBottom="4">
<HStack as="form" onSubmit={ createFromWidgets }>
<FlexBlock>
<SelectControl
label={ __( 'Import widget area' ) }
value={ selectedSidebar }
options={ options }
onChange={ ( value ) => setSelectedSidebar( value ) }
disabled={ ! options.length }
__next36pxDefaultSize
__nextHasNoMarginBottom
/>
</FlexBlock>
<FlexItem
style={ {
marginBottom: '8px',
marginTop: 'auto',
} }
>
<Button
variant="primary"
type="submit"
isBusy={ isBusy }
aria-disabled={ isBusy || ! selectedSidebar }
>
{ __( 'Import' ) }
</Button>
</FlexItem>
</HStack>
</Spacer>
);
}
1 change: 1 addition & 0 deletions packages/block-library/src/template-part/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export default function TemplatePartEdit( {
isEntityAvailable={ isEntityAvailable }
templatePartId={ templatePartId }
defaultWrapper={ areaObject.tagName }
hasInnerBlocks={ innerBlocks.length > 0 }
/>
{ isPlaceholder && (
<TagName { ...blockProps }>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* WordPress dependencies
*/
import { createBlock, parse } from '@wordpress/blocks';

/**
* Converts a widget entity record into a block.
*
* @param {Object} widget The widget entity record.
* @return {Object} a block (converted from the entity record).
*/
export function transformWidgetToBlock( widget ) {
if ( widget.id_base === 'block' ) {
const parsedBlocks = parse( widget.instance.raw.content, {
__unstableSkipAutop: true,
} );
if ( ! parsedBlocks.length ) {
return createBlock( 'core/paragraph', {}, [] );
}

return parsedBlocks[ 0 ];
}

let attributes;
if ( widget._embedded.about[ 0 ].is_multi ) {
attributes = {
idBase: widget.id_base,
instance: widget.instance,
};
} else {
attributes = {
id: widget.id,
};
}

return createBlock( 'core/legacy-widget', attributes, [] );
}
1 change: 1 addition & 0 deletions packages/edit-post/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
"@wordpress/warning": "file:../warning",
"@wordpress/widgets": "file:../widgets",
"classnames": "^2.3.1",
"lodash": "^4.17.21",
"memize": "^1.1.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/edit-post/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { render, unmountComponentAtNode } from '@wordpress/element';
import { dispatch, select } from '@wordpress/data';
import { addFilter } from '@wordpress/hooks';
import { store as preferencesStore } from '@wordpress/preferences';
import { registerLegacyWidgetBlock } from '@wordpress/widgets';

/**
* Internal dependencies
Expand Down Expand Up @@ -115,6 +116,7 @@ export function initializeEditor(
}

registerCoreBlocks();
registerLegacyWidgetBlock( { inserter: false } );
if ( process.env.IS_GUTENBERG_PLUGIN ) {
__experimentalRegisterExperimentalCoreBlocks( {
enableFSEBlocks: settings.__unstableEnableFullSiteEditingBlocks,
Expand Down
1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@wordpress/style-engine": "file:../style-engine",
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
"@wordpress/widgets": "file:../widgets",
"classnames": "^2.3.1",
"colord": "^2.9.2",
"downloadjs": "^1.4.7",
Expand Down

0 comments on commit 1e6538b

Please sign in to comment.