From b528868ea19beea6e89610fcb8c901c979b1c36a Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 9 Jul 2019 12:38:44 +0100 Subject: [PATCH] Featured Category Block (https://github.com/woocommerce/woocommerce-blocks/pull/676) * Block JS * Add block to library and configure build * Update API to return image and link * Limit category selection to 1 * Frontend * Fix variable * Add icon * Rename to isSingle * Standardize naming * fix wrapping issue --- .../js/blocks/featured-category/block.js | 397 ++++++++++++++++++ .../js/blocks/featured-category/editor.scss | 18 + .../js/blocks/featured-category/index.js | 141 +++++++ .../js/blocks/featured-category/style.scss | 130 ++++++ .../assets/js/components/icons/folder-star.js | 20 + .../assets/js/components/icons/index.js | 1 + .../product-category-control/index.js | 8 +- plugins/woocommerce-blocks/src/Assets.php | 1 + .../src/BlockTypes/FeaturedCategory.php | 175 ++++++++ plugins/woocommerce-blocks/src/Library.php | 1 + .../RestApi/Controllers/ProductCategories.php | 49 ++- plugins/woocommerce-blocks/webpack.config.js | 1 + 12 files changed, 931 insertions(+), 11 deletions(-) create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/featured-category/block.js create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/featured-category/editor.scss create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/featured-category/index.js create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/featured-category/style.scss create mode 100644 plugins/woocommerce-blocks/assets/js/components/icons/folder-star.js create mode 100644 plugins/woocommerce-blocks/src/BlockTypes/FeaturedCategory.php diff --git a/plugins/woocommerce-blocks/assets/js/blocks/featured-category/block.js b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/block.js new file mode 100644 index 00000000000..129f6d964c9 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/block.js @@ -0,0 +1,397 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import apiFetch from '@wordpress/api-fetch'; +import { + AlignmentToolbar, + BlockControls, + InnerBlocks, + InspectorControls, + MediaUpload, + MediaUploadCheck, + PanelColorSettings, + withColors, +} from '@wordpress/editor'; +import { + Button, + FocalPointPicker, + IconButton, + PanelBody, + Placeholder, + RangeControl, + ResizableBox, + Spinner, + ToggleControl, + Toolbar, + withSpokenMessages, +} from '@wordpress/components'; +import classnames from 'classnames'; +import { Component, Fragment } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; +import { debounce, isObject } from 'lodash'; +import PropTypes from 'prop-types'; +import { IconFolderStar } from '../../components/icons'; + +/** + * Internal dependencies + */ +import ProductCategoryControl from '../../components/product-category-control'; + +/** + * The min-height for the block content. + */ +const MIN_HEIGHT = wc_product_block_data.min_height; + +/** + * Get the src from a category object, unless null (no image). + * + * @param {object|null} category A product category object from the API. + * @return {string} + */ +function getCategoryImageSrc( category ) { + if ( isObject( category.image ) ) { + return category.image.src; + } + return ''; +} + +/** + * Get the attachment ID from a category object, unless null (no image). + * + * @param {object|null} category A product category object from the API. + * @return {int} + */ +function getCategoryImageID( category ) { + if ( isObject( category.image ) ) { + return category.image.id; + } + return 0; +} + +/** + * Generate a style object given either a product category image from the API or URL to an image. + * + * @param {string} url An image URL. + * @return {object} A style object with a backgroundImage set (if a valid image is provided). + */ +function backgroundImageStyles( url ) { + if ( url ) { + return { backgroundImage: `url(${ url })` }; + } + return {}; +} + +/** + * Convert the selected ratio to the correct background class. + * + * @param {number} ratio Selected opacity from 0 to 100. + * @return {string} The class name, if applicable (not used for ratio 0 or 50). + */ +function dimRatioToClass( ratio ) { + return ratio === 0 || ratio === 50 ? + null : + `has-background-dim-${ 10 * Math.round( ratio / 10 ) }`; +} + +/** + * Component to handle edit mode of "Featured Category". + */ +class FeaturedCategory extends Component { + constructor() { + super( ...arguments ); + this.state = { + category: false, + loaded: false, + }; + + this.debouncedGetCategory = debounce( this.getCategory.bind( this ), 200 ); + } + + componentDidMount() { + this.getCategory(); + } + + componentDidUpdate( prevProps ) { + if ( prevProps.attributes.categoryId !== this.props.attributes.categoryId ) { + this.debouncedGetCategory(); + } + } + + getCategory() { + const { categoryId } = this.props.attributes; + if ( ! categoryId ) { + // We've removed the selected product, or no product is selected yet. + this.setState( { category: false, loaded: true } ); + return; + } + apiFetch( { + path: `/wc/blocks/products/categories/${ categoryId }`, + } ) + .then( ( category ) => { + this.setState( { category, loaded: true } ); + } ) + .catch( () => { + this.setState( { category: false, loaded: true } ); + } ); + } + + getInspectorControls() { + const { + attributes, + setAttributes, + overlayColor, + setOverlayColor, + } = this.props; + + const url = + attributes.mediaSrc || getCategoryImageSrc( this.state.category ); + const { focalPoint = { x: 0.5, y: 0.5 } } = attributes; + + return ( + + + setAttributes( { showDesc: ! attributes.showDesc } ) } + /> + + + setAttributes( { dimRatio: ratio } ) } + min={ 0 } + max={ 100 } + step={ 10 } + /> + { !! FocalPointPicker && !! url && + setAttributes( { focalPoint: value } ) } + /> + } + + + ); + } + + renderEditMode() { + const { attributes, debouncedSpeak, setAttributes } = this.props; + const onDone = () => { + setAttributes( { editMode: false } ); + debouncedSpeak( + __( + 'Showing Featured Product block preview.', + 'woo-gutenberg-products-block' + ) + ); + }; + + return ( + } + label={ __( 'Featured Category', 'woo-gutenberg-products-block' ) } + className="wc-block-featured-category" + > + { __( + 'Visually highlight a product category and encourage prompt action', + 'woo-gutenberg-products-block' + ) } +
+ { + const id = value[ 0 ] ? value[ 0 ].id : 0; + setAttributes( { categoryId: id, mediaId: 0, mediaSrc: '' } ); + } } + multiple={ false } + /> + +
+
+ ); + } + + render() { + const { attributes, isSelected, overlayColor, setAttributes } = this.props; + const { + className, + contentAlign, + dimRatio, + editMode, + focalPoint, + height, + showDesc, + } = attributes; + const { loaded, category } = this.state; + const classes = classnames( + 'wc-block-featured-category', + { + 'is-selected': isSelected, + 'is-loading': ! category && ! loaded, + 'is-not-found': ! category && loaded, + 'has-background-dim': dimRatio !== 0, + }, + dimRatioToClass( dimRatio ), + contentAlign !== 'center' && `has-${ contentAlign }-content`, + className, + ); + const mediaId = attributes.mediaId || getCategoryImageID( category ); + const mediaSrc = attributes.mediaSrc || getCategoryImageSrc( this.state.category ); + const style = !! category ? + backgroundImageStyles( mediaSrc ) : + {}; + if ( overlayColor.color ) { + style.backgroundColor = overlayColor.color; + } + if ( focalPoint ) { + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * + 100 }%`; + } + + const onResizeStop = ( event, direction, elt ) => { + setAttributes( { height: parseInt( elt.style.height ) } ); + }; + + return ( + + + { + setAttributes( { contentAlign: nextAlign } ); + } } + /> + + + { + setAttributes( { mediaId: media.id, mediaSrc: media.url } ); + } } + allowedTypes={ [ 'image' ] } + value={ mediaId } + render={ ( { open } ) => ( + + ) } + /> + + + + { ! attributes.editMode && this.getInspectorControls() } + { editMode ? ( + this.renderEditMode() + ) : ( + + { !! category ? ( + +
+

+ { showDesc && ( +
+ ) } +
+ +
+
+ + ) : ( + } + label={ __( 'Featured Category', 'woo-gutenberg-products-block' ) } + > + { ! loaded ? ( + + ) : ( + __( 'No product category is selected.', 'woo-gutenberg-products-block' ) + ) } + + ) } + + ) } + + ); + } +} + +FeaturedCategory.propTypes = { + /** + * The attributes for this block. + */ + attributes: PropTypes.object.isRequired, + /** + * Whether this block is currently active. + */ + isSelected: PropTypes.bool.isRequired, + /** + * The register block name. + */ + name: PropTypes.string.isRequired, + /** + * A callback to update attributes. + */ + setAttributes: PropTypes.func.isRequired, + // from withColors + overlayColor: PropTypes.object, + setOverlayColor: PropTypes.func.isRequired, + // from withSpokenMessages + debouncedSpeak: PropTypes.func.isRequired, +}; + +export default compose( [ + withColors( { overlayColor: 'background-color' } ), + withSpokenMessages, +] )( FeaturedCategory ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/featured-category/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/editor.scss new file mode 100644 index 00000000000..ac5a43da5bc --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/editor.scss @@ -0,0 +1,18 @@ +.wc-block-featured-category { + &.components-placeholder { + // Reset the background for the placeholders. + background-color: rgba( 139, 139, 150, .1 ); + } + + .components-resizable-box__handle { + z-index: 10; + } + + .components-placeholder__label svg { + fill: currentColor; + margin-right: 1ch; + } +} +.wc-block-featured-category__selection { + width: 100%; +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/featured-category/index.js b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/index.js new file mode 100644 index 00000000000..89d4b672878 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/index.js @@ -0,0 +1,141 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { InnerBlocks } from '@wordpress/editor'; +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import './style.scss'; +import './editor.scss'; +import Block from './block'; +import { IconFolderStar } from '../../components/icons'; + +/** + * Register and run the "Featured Category" block. + */ +registerBlockType( 'woocommerce/featured-category', { + title: __( 'Featured Category', 'woo-gutenberg-products-block' ), + icon: { + src: , + foreground: '#96588a', + }, + category: 'woocommerce', + keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ], + description: __( + 'Visually highlight a product category and encourage prompt action.', + 'woo-gutenberg-products-block' + ), + supports: { + align: [ 'wide', 'full' ], + }, + attributes: { + /** + * Alignment of content inside block. + */ + contentAlign: { + type: 'string', + default: 'center', + }, + + /** + * Percentage opacity of overlay. + */ + dimRatio: { + type: 'number', + default: 50, + }, + + /** + * Toggle for edit mode in the block preview. + */ + editMode: { + type: 'boolean', + default: true, + }, + + /** + * Focus point for the background image + */ + focalPoint: { + type: 'object', + }, + + /** + * A fixed height for the block. + */ + height: { + type: 'number', + default: wc_product_block_data.default_height, + }, + + /** + * ID for a custom image, overriding the product's featured image. + */ + mediaId: { + type: 'number', + default: 0, + }, + + /** + * URL for a custom image, overriding the product's featured image. + */ + mediaSrc: { + type: 'string', + default: '', + }, + + /** + * The overlay color, from the color list. + */ + overlayColor: { + type: 'string', + }, + + /** + * The overlay color, if a custom color value. + */ + customOverlayColor: { + type: 'string', + }, + + /** + * Text for the category link. + */ + linkText: { + type: 'string', + default: __( 'Shop now', 'woo-gutenberg-products-block' ), + }, + + /** + * The category ID to display. + */ + categoryId: { + type: 'number', + }, + + /** + * Show the category description. + */ + showDesc: { + type: 'boolean', + default: true, + }, + }, + + /** + * Renders and manages the block. + */ + edit( props ) { + return ; + }, + + /** + * Block content is rendered in PHP, not via save function. + */ + save() { + return ; + }, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/featured-category/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/style.scss new file mode 100644 index 00000000000..f0cf5368787 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/featured-category/style.scss @@ -0,0 +1,130 @@ +.wc-block-featured-category { + position: relative; + background-color: $black; + background-size: cover; + background-position: center center; + width: 100%; + margin: 0 0 1.5em 0; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + align-content: center; + + .wc-block-featured-category__wrapper { + overflow: hidden; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + align-content: center; + } + + &.has-left-content { + justify-content: flex-start; + + .wc-block-featured-category__title, + .wc-block-featured-category__description, + .wc-block-featured-category__price { + margin-left: 0; + text-align: left; + } + } + + &.has-right-content { + justify-content: flex-end; + + .wc-block-featured-category__title, + .wc-block-featured-category__description, + .wc-block-featured-category__price { + margin-right: 0; + text-align: right; + } + } + + .wc-block-featured-category__title, + .wc-block-featured-category__description, + .wc-block-featured-category__price { + color: $white; + line-height: 1.25; + margin-bottom: 0; + text-align: center; + + a, + a:hover, + a:focus, + a:active { + color: $white; + } + } + + .wc-block-featured-category__title, + .wc-block-featured-category__description, + .wc-block-featured-category__price, + .wc-block-featured-category__link { + width: 100%; + padding: 0 48px 16px 48px; + z-index: 1; + } + + .wc-block-featured-category__title { + margin-top: 0; + + &:before { + display: none; + } + } + + .wc-block-featured-category__description { + p { + margin: 0; + } + } + + &.has-background-dim::before { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-color: inherit; + opacity: 0.5; + z-index: 1; + } + + @for $i from 1 through 10 { + &.has-background-dim.has-background-dim-#{ $i * 10 }::before { + opacity: $i * 0.1; + } + } + + // Apply max-width to floated items that have no intrinsic width + &.alignleft, + &.alignright { + max-width: $content-width / 2; + width: 100%; + } + + // Using flexbox without an assigned height property breaks vertical center alignment in IE11. + // Appending an empty ::after element tricks IE11 into giving the cover image an implicit height, which sidesteps this issue. + &::after { + display: block; + content: ""; + font-size: 0; + min-height: inherit; + + // IE doesn't support flex so omit that. + @supports (position: sticky) { + content: none; + } + } + + // Aligned cover blocks should not use our global alignment rules + &.aligncenter, + &.alignleft, + &.alignright { + display: flex; + } +} diff --git a/plugins/woocommerce-blocks/assets/js/components/icons/folder-star.js b/plugins/woocommerce-blocks/assets/js/components/icons/folder-star.js new file mode 100644 index 00000000000..89f7374622d --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/components/icons/folder-star.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { Icon } from '@wordpress/components'; + +export default () => ( + + + + + } + /> +); diff --git a/plugins/woocommerce-blocks/assets/js/components/icons/index.js b/plugins/woocommerce-blocks/assets/js/components/icons/index.js index 1a8d162bae9..af00dc2c9b8 100644 --- a/plugins/woocommerce-blocks/assets/js/components/icons/index.js +++ b/plugins/woocommerce-blocks/assets/js/components/icons/index.js @@ -2,6 +2,7 @@ export { default as IconCheckChecked } from './checkbox-checked'; export { default as IconCheckUnchecked } from './checkbox-unchecked'; export { default as IconFolder } from './folder'; +export { default as IconFolderStar } from './folder-star'; export { default as IconNewReleases } from './new-releases'; export { default as IconRadioSelected } from './radio-selected'; export { default as IconRadioUnselected } from './radio-unselected'; diff --git a/plugins/woocommerce-blocks/assets/js/components/product-category-control/index.js b/plugins/woocommerce-blocks/assets/js/components/product-category-control/index.js index 63916966bd9..b313b076893 100644 --- a/plugins/woocommerce-blocks/assets/js/components/product-category-control/index.js +++ b/plugins/woocommerce-blocks/assets/js/components/product-category-control/index.js @@ -74,7 +74,7 @@ class ProductCategoryControl extends Component { render() { const { list, loading } = this.state; - const { onChange, onOperatorChange, operator, selected } = this.props; + const { onChange, onOperatorChange, operator, selected, isSingle } = this.props; const messages = { clear: __( 'Clear all product categories', 'woo-gutenberg-products-block' ), @@ -114,6 +114,7 @@ class ProductCategoryControl extends Component { renderItem={ this.renderItem } messages={ messages } isHierarchical + isSingle={ isSingle } /> { ( !! onOperatorChange ) && (
@@ -158,10 +159,15 @@ ProductCategoryControl.propTypes = { * The list of currently selected category IDs. */ selected: PropTypes.array.isRequired, + /** + * Allow only a single selection. Defaults to false. + */ + isSingle: PropTypes.bool, }; ProductCategoryControl.defaultProps = { operator: 'any', + isSingle: false, }; export default ProductCategoryControl; diff --git a/plugins/woocommerce-blocks/src/Assets.php b/plugins/woocommerce-blocks/src/Assets.php index 0f69c1401f5..79c44e74b6b 100644 --- a/plugins/woocommerce-blocks/src/Assets.php +++ b/plugins/woocommerce-blocks/src/Assets.php @@ -47,6 +47,7 @@ class Assets { self::register_script( 'wc-product-top-rated', plugins_url( 'build/product-top-rated.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) ); self::register_script( 'wc-products-by-attribute', plugins_url( 'build/products-by-attribute.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) ); self::register_script( 'wc-featured-product', plugins_url( 'build/featured-product.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) ); + self::register_script( 'wc-featured-category', plugins_url( 'build/featured-category.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) ); self::register_script( 'wc-product-categories', plugins_url( 'build/product-categories.js', __DIR__ ), array( 'wc-vendors', 'wc-packages', 'wc-blocks' ) ); } diff --git a/plugins/woocommerce-blocks/src/BlockTypes/FeaturedCategory.php b/plugins/woocommerce-blocks/src/BlockTypes/FeaturedCategory.php new file mode 100644 index 00000000000..6bc85cba012 --- /dev/null +++ b/plugins/woocommerce-blocks/src/BlockTypes/FeaturedCategory.php @@ -0,0 +1,175 @@ + 'none', + 'contentAlign' => 'center', + 'dimRatio' => 50, + 'focalPoint' => false, + 'height' => false, + 'mediaId' => 0, + 'mediaSrc' => '', + 'showDesc' => true, + ); + + /** + * Render the Featured Category block. + * + * @param array $attributes Block attributes. Default empty array. + * @param string $content Block content. Default empty string. + * @return string Rendered block type output. + */ + public function render( $attributes = array(), $content = '' ) { + $id = isset( $attributes['categoryId'] ) ? (int) $attributes['categoryId'] : 0; + $category = get_term( $id, 'product_cat' ); + if ( ! $category ) { + return ''; + } + $attributes = wp_parse_args( $attributes, $this->defaults ); + if ( ! $attributes['height'] ) { + $attributes['height'] = wc_get_theme_support( 'featured_block::default_height', 500 ); + } + + $title = sprintf( + '', + wp_kses_post( $category->name ) + ); + + $desc_str = sprintf( + '', + wc_format_content( $category->description ) + ); + + $output = sprintf( '
', $this->get_classes( $attributes ), $this->get_styles( $attributes, $category ) ); + + $output .= $title; + if ( $attributes['showDesc'] ) { + $output .= $desc_str; + } + $output .= ''; + $output .= '
'; + + return $output; + } + + /** + * Get the styles for the wrapper element (background image, color). + * + * @param array $attributes Block attributes. Default empty array. + * @param \WP_Term $category Term object. + * @return string + */ + public function get_styles( $attributes, $category ) { + $style = ''; + $image_size = 'large'; + if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) { + $image_size = 'full'; + } + + if ( $attributes['mediaId'] ) { + $image = wp_get_attachment_image_url( $attributes['mediaId'], $image_size ); + } else { + $image = $this->get_image( $category, $image_size ); + } + + if ( ! empty( $image ) ) { + $style .= sprintf( 'background-image:url(%s);', esc_url( $image ) ); + } + + if ( isset( $attributes['customOverlayColor'] ) ) { + $style .= sprintf( 'background-color:%s;', esc_attr( $attributes['customOverlayColor'] ) ); + } + + if ( isset( $attributes['height'] ) ) { + $style .= sprintf( 'min-height:%dpx;', intval( $attributes['height'] ) ); + } + + if ( is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] ) ) { + $style .= sprintf( + 'background-position: %s%% %s%%', + $attributes['focalPoint']['x'] * 100, + $attributes['focalPoint']['y'] * 100 + ); + } + + return $style; + } + + /** + * Get class names for the block container. + * + * @param array $attributes Block attributes. Default empty array. + * @return string + */ + public function get_classes( $attributes ) { + $classes = array( 'wc-block-' . $this->block_name ); + + if ( isset( $attributes['align'] ) ) { + $classes[] = "align{$attributes['align']}"; + } + + if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) { + $classes[] = 'has-background-dim'; + + if ( 50 !== $attributes['dimRatio'] ) { + $classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 ); + } + } + + if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) { + $classes[] = "has-{$attributes['contentAlign']}-content"; + } + + if ( isset( $attributes['overlayColor'] ) ) { + $classes[] = "has-{$attributes['overlayColor']}-background-color"; + } + + if ( isset( $attributes['className'] ) ) { + $classes[] = $attributes['className']; + } + + return implode( $classes, ' ' ); + } + + /** + * Returns the main product image URL. + * + * @param \WP_Term $category Term object. + * @param string $size Image size, defaults to 'full'. + * @return string + */ + public function get_image( $category, $size = 'full' ) { + $image = ''; + $image_id = get_term_meta( $category->term_id, 'thumbnail_id', true ); + + if ( $image_id ) { + $image = wp_get_attachment_image_url( $image_id, $size ); + } + + return $image; + } +} diff --git a/plugins/woocommerce-blocks/src/Library.php b/plugins/woocommerce-blocks/src/Library.php index d2b4ca0c5f1..f9662deafd4 100644 --- a/plugins/woocommerce-blocks/src/Library.php +++ b/plugins/woocommerce-blocks/src/Library.php @@ -26,6 +26,7 @@ class Library { */ public static function register_blocks() { $blocks = [ + 'FeaturedCategory', 'FeaturedProduct', 'HandpickedProducts', 'ProductBestSellers', diff --git a/plugins/woocommerce-blocks/src/RestApi/Controllers/ProductCategories.php b/plugins/woocommerce-blocks/src/RestApi/Controllers/ProductCategories.php index b8a7965165d..eec70eb707e 100644 --- a/plugins/woocommerce-blocks/src/RestApi/Controllers/ProductCategories.php +++ b/plugins/woocommerce-blocks/src/RestApi/Controllers/ProductCategories.php @@ -107,13 +107,33 @@ class ProductCategories extends WC_REST_Product_Categories_Controller { */ public function prepare_item_for_response( $item, $request ) { $data = array( - 'id' => (int) $item->term_id, - 'name' => $item->name, - 'slug' => $item->slug, - 'parent' => (int) $item->parent, - 'count' => (int) $item->count, + 'id' => (int) $item->term_id, + 'name' => $item->name, + 'slug' => $item->slug, + 'parent' => (int) $item->parent, + 'count' => (int) $item->count, + 'description' => $item->description, + 'image' => null, + 'permalink' => get_term_link( $item->term_id, 'product_cat' ), ); + $image_id = get_term_meta( $item->term_id, 'thumbnail_id', true ); + + if ( $image_id ) { + $attachment = get_post( $image_id ); + + $data['image'] = array( + 'id' => (int) $image_id, + 'date_created' => wc_rest_prepare_date_response( $attachment->post_date ), + 'date_created_gmt' => wc_rest_prepare_date_response( $attachment->post_date_gmt ), + 'date_modified' => wc_rest_prepare_date_response( $attachment->post_modified ), + 'date_modified_gmt' => wc_rest_prepare_date_response( $attachment->post_modified_gmt ), + 'src' => wp_get_attachment_url( $image_id ), + 'name' => get_the_title( $attachment ), + 'alt' => get_post_meta( $image_id, '_wp_attachment_image_alt', true ), + ); + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -138,11 +158,20 @@ class ProductCategories extends WC_REST_Product_Categories_Controller { 'properties' => array(), ); - $schema['properties']['id'] = $raw_schema['properties']['id']; - $schema['properties']['name'] = $raw_schema['properties']['name']; - $schema['properties']['slug'] = $raw_schema['properties']['slug']; - $schema['properties']['parent'] = $raw_schema['properties']['parent']; - $schema['properties']['count'] = $raw_schema['properties']['count']; + $schema['properties']['id'] = $raw_schema['properties']['id']; + $schema['properties']['name'] = $raw_schema['properties']['name']; + $schema['properties']['slug'] = $raw_schema['properties']['slug']; + $schema['properties']['parent'] = $raw_schema['properties']['parent']; + $schema['properties']['count'] = $raw_schema['properties']['count']; + $schema['properties']['description'] = $raw_schema['properties']['description']; + $schema['properties']['image'] = $raw_schema['properties']['image']; + $schema['properties']['permalink'] = array( + 'description' => __( 'Category URL.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ); return $this->add_additional_fields_schema( $schema ); } diff --git a/plugins/woocommerce-blocks/webpack.config.js b/plugins/woocommerce-blocks/webpack.config.js index f5c26c23c81..3794f751b5f 100644 --- a/plugins/woocommerce-blocks/webpack.config.js +++ b/plugins/woocommerce-blocks/webpack.config.js @@ -38,6 +38,7 @@ const GutenbergBlocksConfig = { 'product-top-rated': './assets/js/blocks/product-top-rated/index.js', 'products-by-attribute': './assets/js/blocks/products-by-attribute/index.js', 'featured-product': './assets/js/blocks/featured-product/index.js', + 'featured-category': './assets/js/blocks/featured-category/index.js', }, output: { path: path.resolve( __dirname, './build/' ),