diff --git a/assets/js/atomic/blocks/index.js b/assets/js/atomic/blocks/index.js index ab691403fc8..8b66f58247a 100644 --- a/assets/js/atomic/blocks/index.js +++ b/assets/js/atomic/blocks/index.js @@ -17,3 +17,4 @@ import './product-elements/add-to-cart-form'; import './product-elements/product-image-gallery'; import './product-elements/product-details'; import './product-elements/related-products'; +import './product-elements/product-meta'; diff --git a/assets/js/atomic/blocks/product-elements/product-meta/block.json b/assets/js/atomic/blocks/product-elements/product-meta/block.json new file mode 100644 index 00000000000..f2d22b4f6b2 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-meta/block.json @@ -0,0 +1,17 @@ +{ + "name": "woocommerce/product-meta", + "version": "1.0.0", + "title": "Product Meta", + "icon": "product", + "description": "Display Product Meta", + "category": "woocommerce", + "supports": { + "align": true, + "reusable": false + }, + "keywords": [ "WooCommerce" ], + "usesContext": [ "postId", "postType", "queryId" ], + "textdomain": "woo-gutenberg-products-block", + "apiVersion": 2, + "$schema": "https://schemas.wp.org/trunk/block.json" +} diff --git a/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx b/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx new file mode 100644 index 00000000000..1058d11faa6 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-meta/edit.tsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; +import { InnerBlockTemplate } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import './editor.scss'; + +const Edit = () => { + const TEMPLATE: InnerBlockTemplate[] = [ + [ + 'core/group', + { layout: { type: 'flex', flexWrap: 'nowrap' } }, + [ + [ + 'woocommerce/product-sku', + { + isDescendentOfSingleProductTemplate: true, + }, + ], + [ + 'core/post-terms', + { + prefix: 'Category: ', + term: 'product_cat', + }, + ], + [ + 'core/post-terms', + { + prefix: 'Tags: ', + term: 'product_tag', + }, + ], + ], + ], + ]; + const blockProps = useBlockProps(); + + return ( +
+ +
+ ); +}; + +export default Edit; diff --git a/assets/js/atomic/blocks/product-elements/product-meta/editor.scss b/assets/js/atomic/blocks/product-elements/product-meta/editor.scss new file mode 100644 index 00000000000..b3872daea3a --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-meta/editor.scss @@ -0,0 +1,4 @@ +.wc-block-editor-related-products__notice { + margin: 10px auto; + max-width: max-content; +} diff --git a/assets/js/atomic/blocks/product-elements/product-meta/index.tsx b/assets/js/atomic/blocks/product-elements/product-meta/index.tsx new file mode 100644 index 00000000000..1db84adc949 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-meta/index.tsx @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { box as icon } from '@wordpress/icons'; +import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; +import { registerBlockSingleProductTemplate } from '@woocommerce/atomic-utils'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import save from './save'; +import metadata from './block.json'; + +registerBlockSingleProductTemplate( { + registerBlockFn: () => { + // @ts-expect-error: `registerBlockType` is a function that is typed in WordPress core. + registerBlockType( metadata, { + icon, + edit, + save, + } ); + }, + unregisterBlockFn: () => { + unregisterBlockType( metadata.name ); + }, + blockName: metadata.name, +} ); diff --git a/assets/js/atomic/blocks/product-elements/product-meta/save.tsx b/assets/js/atomic/blocks/product-elements/product-meta/save.tsx new file mode 100644 index 00000000000..0feb6d8f950 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/product-meta/save.tsx @@ -0,0 +1,17 @@ +/** + * External dependencies + */ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +const Save = () => { + const blockProps = useBlockProps.save(); + + return ( +
+ { /* @ts-expect-error: `InnerBlocks.Content` is a component that is typed in WordPress core*/ } + +
+ ); +}; + +export default Save; diff --git a/assets/js/atomic/blocks/product-elements/sku/attributes.ts b/assets/js/atomic/blocks/product-elements/sku/attributes.ts index d7194c6d347..80b6226b1dd 100644 --- a/assets/js/atomic/blocks/product-elements/sku/attributes.ts +++ b/assets/js/atomic/blocks/product-elements/sku/attributes.ts @@ -12,6 +12,14 @@ export const blockAttributes: BlockAttributes = { type: 'boolean', default: false, }, + isDescendentOfSingleProductTemplate: { + type: 'boolean', + default: false, + }, + showProductSelector: { + type: 'boolean', + default: false, + }, }; export default blockAttributes; diff --git a/assets/js/atomic/blocks/product-elements/sku/block.tsx b/assets/js/atomic/blocks/product-elements/sku/block.tsx index 187bc3d8fe9..33fad6fc911 100644 --- a/assets/js/atomic/blocks/product-elements/sku/block.tsx +++ b/assets/js/atomic/blocks/product-elements/sku/block.tsx @@ -18,29 +18,51 @@ import type { Attributes } from './types'; type Props = Attributes & HTMLAttributes< HTMLDivElement >; +const Preview = ( { + parentClassName, + sku, + className, +}: { + parentClassName: string; + sku: string; + className?: string | undefined; +} ) => ( +
+ { __( 'SKU:', 'woo-gutenberg-products-block' ) }{ ' ' } + { sku } +
+); + const Block = ( props: Props ): JSX.Element | null => { const { className } = props; const { parentClassName } = useInnerBlockLayoutContext(); const { product } = useProductDataContext(); const sku = product.sku; + if ( props.isDescendentOfSingleProductTemplate ) { + return ( + + ); + } + if ( ! sku ) { return null; } return ( -
- { __( 'SKU:', 'woo-gutenberg-products-block' ) }{ ' ' } - { sku } -
+ ); }; diff --git a/assets/js/atomic/blocks/product-elements/sku/edit.tsx b/assets/js/atomic/blocks/product-elements/sku/edit.tsx index 76cf5c20eda..b259af2065f 100644 --- a/assets/js/atomic/blocks/product-elements/sku/edit.tsx +++ b/assets/js/atomic/blocks/product-elements/sku/edit.tsx @@ -1,7 +1,6 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; import type { BlockEditProps } from '@wordpress/blocks'; import EditProductLink from '@woocommerce/editor-components/edit-product-link'; import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types'; @@ -11,8 +10,6 @@ import { useEffect } from '@wordpress/element'; * Internal dependencies */ import Block from './block'; -import withProductSelector from '../shared/with-product-selector'; -import { BLOCK_TITLE, BLOCK_ICON } from './constants'; import type { Attributes } from './types'; const Edit = ( { @@ -39,11 +36,4 @@ const Edit = ( { ); }; -export default withProductSelector( { - icon: BLOCK_ICON, - label: BLOCK_TITLE, - description: __( - 'Choose a product to display its SKU.', - 'woo-gutenberg-products-block' - ), -} )( Edit ); +export default Edit; diff --git a/assets/js/atomic/blocks/product-elements/sku/index.ts b/assets/js/atomic/blocks/product-elements/sku/index.tsx similarity index 66% rename from assets/js/atomic/blocks/product-elements/sku/index.ts rename to assets/js/atomic/blocks/product-elements/sku/index.tsx index ccc5c6dca68..4774bdc03bd 100644 --- a/assets/js/atomic/blocks/product-elements/sku/index.ts +++ b/assets/js/atomic/blocks/product-elements/sku/index.tsx @@ -3,6 +3,7 @@ */ import { registerBlockType } from '@wordpress/blocks'; import type { BlockConfiguration } from '@wordpress/blocks'; +import classnames from 'classnames'; /** * Internal dependencies @@ -16,20 +17,37 @@ import { BLOCK_DESCRIPTION as description, } from './constants'; +const { ancestor, ...configuration } = sharedConfig; + const blockConfig: BlockConfiguration = { - ...sharedConfig, + ...configuration, apiVersion: 2, title, description, icon: { src: icon }, usesContext: [ 'query', 'queryId', 'postId' ], + attributes, ancestor: [ 'woocommerce/all-products', 'woocommerce/single-product', 'core/post-template', + 'woocommerce/product-meta', ], - attributes, edit, + save: () => { + if ( + attributes.isDescendentOfQueryLoop || + attributes.isDescendentOfSingleProductTemplate + ) { + return null; + } + + return ( +
+ ); + }, }; registerBlockType( 'woocommerce/product-sku', { ...blockConfig } ); diff --git a/assets/js/atomic/blocks/product-elements/sku/style.scss b/assets/js/atomic/blocks/product-elements/sku/style.scss index 7da83fbfc84..4cc0eb991fb 100644 --- a/assets/js/atomic/blocks/product-elements/sku/style.scss +++ b/assets/js/atomic/blocks/product-elements/sku/style.scss @@ -1,6 +1,4 @@ .wc-block-components-product-sku { - margin-top: 0; - margin-bottom: $gap-small; display: block; text-transform: uppercase; @include font-size(small); diff --git a/assets/js/atomic/blocks/product-elements/sku/types.ts b/assets/js/atomic/blocks/product-elements/sku/types.ts index a7d3a20b916..9651b739436 100644 --- a/assets/js/atomic/blocks/product-elements/sku/types.ts +++ b/assets/js/atomic/blocks/product-elements/sku/types.ts @@ -1,4 +1,6 @@ export interface Attributes { productId: number; isDescendentOfQueryLoop: boolean; + isDescendentOfSingleProductTemplate: boolean; + showProductSelector: boolean; } diff --git a/src/BlockTemplatesController.php b/src/BlockTemplatesController.php index a08918ef4e1..e1c5c82c7d8 100644 --- a/src/BlockTemplatesController.php +++ b/src/BlockTemplatesController.php @@ -327,7 +327,8 @@ function( $template ) { } if ( 'single-product' === $template->slug ) { - if ( ! is_admin() ) { + if ( ! is_admin() && ! BlockTemplateUtils::template_has_legacy_template_block( $template ) ) { + $new_content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content ); $template->content = $new_content; } diff --git a/src/BlockTypes/RelatedProducts.php b/src/BlockTypes/RelatedProducts.php index 4445376407f..eaeb5808381 100644 --- a/src/BlockTypes/RelatedProducts.php +++ b/src/BlockTypes/RelatedProducts.php @@ -19,7 +19,6 @@ class RelatedProducts extends AbstractBlock { */ protected $parsed_block; - /** * Initialize this block type. * @@ -35,7 +34,6 @@ protected function initialize() { 10, 2 ); - } /** @@ -45,7 +43,6 @@ protected function register_block_type_assets() { return null; } - /** * Update the query for the product query block. * @@ -70,8 +67,6 @@ public function update_query( $pre_render, $parsed_block ) { } } - - /** * Return a custom query based on attributes, filters and global WP_Query. * @@ -92,6 +87,4 @@ public function build_query( $query ) { 'post_status' => 'publish', ); } - - } diff --git a/src/Templates/SingleProductTemplateCompatibility.php b/src/Templates/SingleProductTemplateCompatibility.php index 48c2839cef2..ca60ecdf3a2 100644 --- a/src/Templates/SingleProductTemplateCompatibility.php +++ b/src/Templates/SingleProductTemplateCompatibility.php @@ -27,12 +27,6 @@ public function inject_hooks( $block_content, $block ) { $this->remove_default_hooks(); - $first_or_last_block_content = $this->inject_hook_to_first_and_last_blocks( $block_content, $block ); - - if ( isset( $first_or_last_block_content ) ) { - return $first_or_last_block_content; - } - $block_name = $block['blockName']; $block_hooks = array_filter( @@ -42,6 +36,12 @@ function( $hook ) use ( $block_name ) { } ); + $first_or_last_block_content = $this->inject_hook_to_first_and_last_blocks( $block_content, $block, $block_hooks ); + + if ( isset( $first_or_last_block_content ) ) { + return $first_or_last_block_content; + } + return sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( $block_hooks, 'before' ), @@ -63,9 +63,10 @@ function( $hook ) use ( $block_name ) { * * @param mixed $block_content The rendered block content. * @param mixed $block The parsed block data. + * @param array $block_hooks The hooks that should be injected to the block. * @return string */ - private function inject_hook_to_first_and_last_blocks( $block_content, $block ) { + private function inject_hook_to_first_and_last_blocks( $block_content, $block, $block_hooks ) { $first_block_hook = array( 'before' => array( 'woocommerce_before_main_content' => $this->hook_data['woocommerce_before_main_content'], @@ -91,6 +92,7 @@ private function inject_hook_to_first_and_last_blocks( $block_content, $block ) $this->get_hooks_buffer( array_merge( $first_block_hook['before'], + $block_hooks, $last_block_hook['before'] ), 'before' @@ -99,6 +101,7 @@ private function inject_hook_to_first_and_last_blocks( $block_content, $block ) $this->get_hooks_buffer( array_merge( $first_block_hook['after'], + $block_hooks, $last_block_hook['after'] ), 'after' @@ -110,12 +113,18 @@ private function inject_hook_to_first_and_last_blocks( $block_content, $block ) return sprintf( '%1$s%2$s%3$s', $this->get_hooks_buffer( - $first_block_hook['before'], + array_merge( + $first_block_hook['before'], + $block_hooks + ), 'before' ), $block_content, $this->get_hooks_buffer( - $first_block_hook['after'], + array_merge( + $first_block_hook['after'], + $block_hooks + ), 'after' ) ); @@ -124,10 +133,19 @@ private function inject_hook_to_first_and_last_blocks( $block_content, $block ) if ( isset( $block['attrs'][ self::IS_LAST_BLOCK ] ) ) { return sprintf( '%1$s%2$s%3$s', - $this->get_hooks_buffer( $last_block_hook['before'], 'before' ), + $this->get_hooks_buffer( + array_merge( + $last_block_hook['before'], + $block_hooks + ), + 'before' + ), $block_content, $this->get_hooks_buffer( - $last_block_hook['after'], + array_merge( + $block_hooks, + $last_block_hook['after'] + ), 'after' ) ); @@ -208,6 +226,16 @@ protected function set_hook_data() { 'position' => 'after', 'hooked' => array(), ), + 'woocommerce_product_meta_start' => array( + 'block_name' => 'woocommerce/product-meta', + 'position' => 'before', + 'hooked' => array(), + ), + 'woocommerce_product_meta_end' => array( + 'block_name' => 'woocommerce/product-meta', + 'position' => 'after', + 'hooked' => array(), + ), 'woocommerce_share' => array( 'block_name' => 'woocommerce/product-details', 'position' => 'before',