diff --git a/assets/js/blocks/products/all-products/index.js b/assets/js/blocks/products/all-products/index.js index ac52b3d50ee..62c3859bffe 100644 --- a/assets/js/blocks/products/all-products/index.js +++ b/assets/js/blocks/products/all-products/index.js @@ -10,14 +10,11 @@ import Gridicon from 'gridicons'; * Internal dependencies */ import Editor from './edit'; -import sharedAttributes from '../attributes'; +import { attributes as sharedAttributes, defaults } from '../attributes'; import { getBlockClassName } from '../utils.js'; import '../../../atomic/blocks/product'; -/** - * Register and run the "All Products" block. - */ -registerBlockType( 'woocommerce/all-products', { +const blockSettings = { title: __( 'All Products', 'woo-gutenberg-products-block' ), icon: { src: , @@ -39,10 +36,8 @@ registerBlockType( 'woocommerce/all-products', { isPreview: true, }, }, - attributes: { - ...sharedAttributes, - }, - + attributes: sharedAttributes, + defaults, /** * Renders and manages the block. * @@ -51,7 +46,6 @@ registerBlockType( 'woocommerce/all-products', { edit( props ) { return ; }, - /** * Save the props to post content. * @@ -59,9 +53,11 @@ registerBlockType( 'woocommerce/all-products', { */ save( { attributes } ) { const data = { - 'data-attributes': JSON.stringify( attributes ), + 'data-attributes': JSON.stringify( + attributes, + Object.keys( attributes ).sort() + ), }; - return (
); }, +}; + +/** + * Register and run the "All Products" block. + */ +registerBlockType( 'woocommerce/all-products', { + ...blockSettings, + /** + * Deprecation rule to handle the previous default rows which was 1 instead of 3. + */ + deprecated: [ + { + attributes: Object.assign( {}, blockSettings.attributes, { + rows: { type: 'number', default: 1 }, + } ), + save( { attributes } ) { + const data = { + 'data-attributes': JSON.stringify( attributes ), + }; + return ( +
+ +
+ ); + }, + }, + ], } ); diff --git a/assets/js/blocks/products/attributes.js b/assets/js/blocks/products/attributes.js index 2d143fd20c7..f8af9c3675f 100644 --- a/assets/js/blocks/products/attributes.js +++ b/assets/js/blocks/products/attributes.js @@ -8,57 +8,55 @@ import { DEFAULT_COLUMNS, DEFAULT_ROWS } from '@woocommerce/block-settings'; */ import { DEFAULT_PRODUCT_LIST_LAYOUT } from './base-utils'; -export default { +export const defaults = { + columns: DEFAULT_COLUMNS, + rows: DEFAULT_ROWS, + alignButtons: false, + contentVisibility: { + orderBy: true, + }, + orderby: 'date', + layoutConfig: DEFAULT_PRODUCT_LIST_LAYOUT, + isPreview: false, +}; + +export const attributes = { /** * Number of columns. */ columns: { type: 'number', - default: DEFAULT_COLUMNS, }, - /** * Number of rows. */ rows: { type: 'number', - default: DEFAULT_ROWS, }, - /** * How to align cart buttons. */ alignButtons: { type: 'boolean', - default: false, }, - /** * Content visibility setting */ contentVisibility: { type: 'object', - default: { - orderBy: true, - }, }, - /** * Order to use for the products listing. */ orderby: { type: 'string', - default: 'date', }, - /** * Layout config. */ layoutConfig: { type: 'array', - default: DEFAULT_PRODUCT_LIST_LAYOUT, }, - /** * Are we previewing? */ diff --git a/assets/js/blocks/products/utils.js b/assets/js/blocks/products/utils.js index 43f019a4003..3f2ea04f406 100644 --- a/assets/js/blocks/products/utils.js +++ b/assets/js/blocks/products/utils.js @@ -11,11 +11,11 @@ export const getBlockClassName = ( blockClassName, attributes ) => { const { className, contentVisibility } = attributes; return classNames( blockClassName, className, { - 'has-image': contentVisibility.image, - 'has-title': contentVisibility.title, - 'has-rating': contentVisibility.rating, - 'has-price': contentVisibility.price, - 'has-button': contentVisibility.button, + 'has-image': contentVisibility && contentVisibility.image, + 'has-title': contentVisibility && contentVisibility.title, + 'has-rating': contentVisibility && contentVisibility.rating, + 'has-price': contentVisibility && contentVisibility.price, + 'has-button': contentVisibility && contentVisibility.button, } ); }; diff --git a/assets/js/filters/block-list-block.js b/assets/js/filters/block-list-block.js new file mode 100644 index 00000000000..d98089262d1 --- /dev/null +++ b/assets/js/filters/block-list-block.js @@ -0,0 +1,84 @@ +/** + * External dependencies + */ +import { Component } from '@wordpress/element'; +import { createHigherOrderComponent } from '@wordpress/compose'; +const { getBlockType } = wp.blocks; +const { addFilter } = wp.hooks; + +/** + * withDefaultAttributes HOC for editor.BlockListBlock. + * + * @param object BlockListBlock The BlockListBlock element. + */ +const withDefaultAttributes = createHigherOrderComponent( + ( BlockListBlock ) => { + class WrappedComponent extends Component { + constructor() { + super( ...arguments ); + + const blockType = getBlockType( this.props.block.name ); + const attributes = Object.assign( + {}, + this.props.attributes || {} + ); + + if ( + this.props.block.name.startsWith( 'woocommerce/' ) && + typeof blockType.attributes !== 'undefined' && + typeof blockType.defaults !== 'undefined' + ) { + Object.keys( blockType.attributes ).map( ( key ) => { + if ( + typeof attributes[ key ] === 'undefined' && + typeof blockType.defaults[ key ] !== 'undefined' + ) { + attributes[ key ] = blockType.defaults[ key ]; + } + return key; + } ); + } + + this.attributesWithDefaults = attributes; + } + + componentDidMount() { + const { block, setAttributes } = this.props; + + if ( block.name.startsWith( 'woocommerce/' ) ) { + setAttributes( this.attributesWithDefaults ); + } + } + + render() { + return ( + + ); + } + } + return WrappedComponent; + }, + 'withDefaultAttributes' +); + +/** + * Hook into `editor.BlockListBlock` to set default attributes (if blocks + * define them separately) when a block is inserted. + * + * This is a workaround for Gutenberg which does not save "default" attributes + * to the post, which means if defaults change, all existing blocks change too. + * + * See https://github.com/WordPress/gutenberg/issues/7342 + * + * To use this, the block name needs a `woocommerce/` prefix, and as well + * as defining `attributes` during block registration, you must also declare an + * array called `defaults`. Defaults should be omitted from `attributes`. + */ +addFilter( + 'editor.BlockListBlock', + 'woocommerce-blocks/block-list-block', + withDefaultAttributes +); diff --git a/assets/js/filters/get-block-attributes.js b/assets/js/filters/get-block-attributes.js new file mode 100644 index 00000000000..3b02d3db013 --- /dev/null +++ b/assets/js/filters/get-block-attributes.js @@ -0,0 +1,44 @@ +const { addFilter } = wp.hooks; + +/** + * Adjust attributes on load to set defaults so default attributes get saved. + * + * @param {Object} blockAttributes Original block attributes. + * @param {Object} blockType Block type settings. + * + * @return {Object} Filtered block attributes. + */ +const setBlockAttributeDefaults = ( blockAttributes, blockType ) => { + if ( blockType.name.startsWith( 'woocommerce/' ) ) { + Object.keys( blockType.attributes ).map( ( key ) => { + if ( + typeof blockAttributes[ key ] === 'undefined' && + typeof blockType.defaults !== 'undefined' && + typeof blockType.defaults[ key ] !== 'undefined' + ) { + blockAttributes[ key ] = blockType.defaults[ key ]; + } + return key; + } ); + } + return blockAttributes; +}; + +/** + * Hook into `blocks.getBlockAttributes` to set default attributes (if blocks + * define them separately) when a block is loaded. + * + * This is a workaround for Gutenberg which does not save "default" attributes + * to the post, which means if defaults change, all existing blocks change too. + * + * See https://github.com/WordPress/gutenberg/issues/7342 + * + * To use this, the block name needs a `woocommerce/` prefix, and as well + * as defining `attributes` during block registration, you must also declare an + * array called `defaults`. Defaults should be omitted from `attributes`. + */ +addFilter( + 'blocks.getBlockAttributes', + 'woocommerce-blocks/get-block-attributes', + setBlockAttributeDefaults +); diff --git a/assets/js/hocs/index.js b/assets/js/hocs/index.js index 0c08301c170..5c14b873b5b 100644 --- a/assets/js/hocs/index.js +++ b/assets/js/hocs/index.js @@ -5,7 +5,5 @@ export { default as withFeedbackPrompt } from './with-feedback-prompt'; export { default as withProduct } from './with-product'; export { default as withProductVariations } from './with-product-variations'; export { default as withSearchedProducts } from './with-searched-products'; -export { - default as withTransformSingleSelectToMultipleSelect, -} from './with-transform-single-select-to-multiple-select'; +export { default as withTransformSingleSelectToMultipleSelect } from './with-transform-single-select-to-multiple-select'; export { default as withRestApiHydration } from './with-rest-api-hydration'; diff --git a/assets/js/index.js b/assets/js/index.js index 7bbad17cf70..d8bad76be01 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -9,6 +9,8 @@ import { __ } from '@wordpress/i18n'; */ import '../css/editor.scss'; import '../css/style.scss'; +import './filters/block-list-block'; +import './filters/get-block-attributes'; import { IconWoo } from './components/icons'; setCategories( [ diff --git a/assets/js/settings/blocks/constants.js b/assets/js/settings/blocks/constants.js index 7be3699f87d..a83160ddbd3 100644 --- a/assets/js/settings/blocks/constants.js +++ b/assets/js/settings/blocks/constants.js @@ -13,7 +13,7 @@ export const MIN_COLUMNS = getSetting( 'min_columns', 1 ); export const DEFAULT_COLUMNS = getSetting( 'default_columns', 3 ); export const MAX_ROWS = getSetting( 'max_rows', 6 ); export const MIN_ROWS = getSetting( 'min_rows', 1 ); -export const DEFAULT_ROWS = getSetting( 'default_rows', 2 ); +export const DEFAULT_ROWS = getSetting( 'default_rows', 3 ); export const MIN_HEIGHT = getSetting( 'min_height', 500 ); export const DEFAULT_HEIGHT = getSetting( 'default_height', 500 ); export const PLACEHOLDER_IMG_SRC = getSetting( 'placeholderImgSrc', '' ); diff --git a/package.json b/package.json index ea81aea7ad0..62fad1a5648 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "sideEffects": [ "*.css", "*.scss", - "./assets/js/atomic/blocks/product/**" + "./assets/js/atomic/blocks/product/**", + "./assets/js/filters/**" ], "repository": { "type": "git", diff --git a/src/Assets.php b/src/Assets.php index 87a444cac7b..af22d47b23d 100644 --- a/src/Assets.php +++ b/src/Assets.php @@ -108,7 +108,7 @@ public static function get_wc_block_data( $settings ) { 'default_columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ), 'min_rows' => wc_get_theme_support( 'product_blocks::min_rows', 1 ), 'max_rows' => wc_get_theme_support( 'product_blocks::max_rows', 6 ), - 'default_rows' => wc_get_theme_support( 'product_blocks::default_rows', 1 ), + 'default_rows' => wc_get_theme_support( 'product_blocks::default_rows', 3 ), 'thumbnail_size' => wc_get_theme_support( 'thumbnail_image_width', 300 ), 'placeholderImgSrc' => wc_placeholder_img_src(), 'min_height' => wc_get_theme_support( 'featured_block::min_height', 500 ), diff --git a/src/BlockTypes/AbstractProductGrid.php b/src/BlockTypes/AbstractProductGrid.php index 1f4fa81e3e9..df5bf802c05 100644 --- a/src/BlockTypes/AbstractProductGrid.php +++ b/src/BlockTypes/AbstractProductGrid.php @@ -46,7 +46,7 @@ protected function get_attributes() { return array( 'className' => $this->get_schema_string(), 'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ), - 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 1 ) ), + 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'categories' => $this->get_schema_list_ids(), 'catOperator' => array( 'type' => 'string', @@ -122,7 +122,7 @@ protected function parse_attributes( $attributes ) { // These should match what's set in JS `registerBlockType`. $defaults = array( 'columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ), - 'rows' => wc_get_theme_support( 'product_blocks::default_rows', 1 ), + 'rows' => wc_get_theme_support( 'product_blocks::default_rows', 3 ), 'alignButtons' => false, 'categories' => array(), 'catOperator' => 'any', diff --git a/src/BlockTypes/ProductTag.php b/src/BlockTypes/ProductTag.php index 4cd882521e9..6f361afad69 100644 --- a/src/BlockTypes/ProductTag.php +++ b/src/BlockTypes/ProductTag.php @@ -45,7 +45,7 @@ protected function get_attributes() { return array( 'className' => $this->get_schema_string(), 'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ), - 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 1 ) ), + 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'contentVisibility' => $this->get_schema_content_visibility(), 'align' => $this->get_schema_align(), 'alignButtons' => $this->get_schema_boolean( false ), diff --git a/src/BlockTypes/ProductsByAttribute.php b/src/BlockTypes/ProductsByAttribute.php index 2ca58458842..310f90dd6fd 100644 --- a/src/BlockTypes/ProductsByAttribute.php +++ b/src/BlockTypes/ProductsByAttribute.php @@ -73,7 +73,7 @@ protected function get_attributes() { 'contentVisibility' => $this->get_schema_content_visibility(), 'editMode' => $this->get_schema_boolean( true ), 'orderby' => $this->get_schema_orderby(), - 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 1 ) ), + 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'isPreview' => $this->get_schema_boolean( false ), ); }