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',