diff --git a/assets/js/blocks/product-elements/component-init.js b/assets/js/blocks/product-elements/component-init.js index c80225fa2..184f78efd 100644 --- a/assets/js/blocks/product-elements/component-init.js +++ b/assets/js/blocks/product-elements/component-init.js @@ -12,3 +12,21 @@ registerBlockComponent( { ) ), } ); + +registerBlockComponent( { + blockName: 'woocommerce-germanized/product-delivery-time', + component: lazy( () => + import( + /* webpackChunkName: "product-delivery-time" */ './delivery-time/frontend' + ) + ), +} ); + +registerBlockComponent( { + blockName: 'woocommerce-germanized/product-tax-info', + component: lazy( () => + import( + /* webpackChunkName: "product-tax-info" */ './tax-info/frontend' + ) + ), +} ); diff --git a/assets/js/blocks/product-elements/delivery-time/block.js b/assets/js/blocks/product-elements/delivery-time/block.js new file mode 100644 index 000000000..e296bae16 --- /dev/null +++ b/assets/js/blocks/product-elements/delivery-time/block.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { withProductDataContext } from '@woocommerce/shared-hocs'; +import PriceLabelBlock from '../shared/price-label-block'; +export default ( props ) => { + props = { ...props, 'labelType': 'delivery-time' }; + + // It is necessary because this block has to support serveral contexts: + // - Inside `All Products Block` -> `withProductDataContext` HOC + // - Inside `Products Block` -> Gutenberg Context + // - Inside `Single Product Template` -> Gutenberg Context + // - Without any parent -> `WithSelector` and `withProductDataContext` HOCs + // For more details, check https://github.com/woocommerce/woocommerce-blocks/pull/8609 + if ( props.isDescendentOfSingleProductTemplate ) { + return ; + } + return withProductDataContext( PriceLabelBlock )( props ); +}; diff --git a/assets/js/blocks/product-elements/delivery-time/edit.js b/assets/js/blocks/product-elements/delivery-time/edit.js new file mode 100644 index 000000000..81c99099c --- /dev/null +++ b/assets/js/blocks/product-elements/delivery-time/edit.js @@ -0,0 +1,65 @@ +/** + * External dependencies + */ +import { + AlignmentToolbar, + BlockControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Block from './block'; +import { useIsDescendentOfSingleProductTemplate } from '../shared/use-is-descendent-of-single-product-template'; +const UnitPriceEdit = ( { + attributes, + setAttributes, + context, +} ) => { + const blockProps = useBlockProps(); + const blockAttrs = { + ...attributes, + ...context, + }; + const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); + + let { isDescendentOfSingleProductTemplate } = + useIsDescendentOfSingleProductTemplate( { isDescendentOfQueryLoop } ); + + if ( isDescendentOfQueryLoop ) { + isDescendentOfSingleProductTemplate = false; + } + + useEffect( + () => + setAttributes( { + isDescendentOfQueryLoop, + isDescendentOfSingleProductTemplate, + } ), + [ + isDescendentOfQueryLoop, + isDescendentOfSingleProductTemplate, + setAttributes, + ] + ); + + return ( + <> + + { + setAttributes( { textAlign } ); + } } + /> + +
+ +
+ + ); +}; + +export default UnitPriceEdit; \ No newline at end of file diff --git a/assets/js/blocks/product-elements/delivery-time/frontend.js b/assets/js/blocks/product-elements/delivery-time/frontend.js new file mode 100644 index 000000000..60ff2a812 --- /dev/null +++ b/assets/js/blocks/product-elements/delivery-time/frontend.js @@ -0,0 +1,5 @@ +/** + * Internal dependencies + */ +import Block from './block'; +export default Block; diff --git a/assets/js/blocks/product-elements/delivery-time/index.js b/assets/js/blocks/product-elements/delivery-time/index.js new file mode 100644 index 000000000..0e527fd8d --- /dev/null +++ b/assets/js/blocks/product-elements/delivery-time/index.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import { currencyDollar, Icon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import sharedConfig from '../shared/config'; +import edit from './edit'; + +const { ancestor, ...configuration } = sharedConfig; + +const blockConfig = { + ...configuration, + apiVersion: 2, + title: __( 'Delivery Time', 'woocommerce-germanized' ), + description: __( 'Inserts the product\'s delivery time.', 'woocommerce-germanized' ), + usesContext: [ 'query', 'queryId', 'postId' ], + icon: { src: }, + + supports: { + ...sharedConfig.supports, + ...( { + __experimentalSelector: + '.wp-block-woocommerce-gzd-product-delivery-time .wc-gzd-block-components-product-delivery-time', + } ) + }, + edit, +}; + +registerBlockType( 'woocommerce-germanized/product-delivery-time', blockConfig ); diff --git a/assets/js/blocks/product-elements/index.js b/assets/js/blocks/product-elements/index.js index 863839a84..5964ff0c7 100644 --- a/assets/js/blocks/product-elements/index.js +++ b/assets/js/blocks/product-elements/index.js @@ -1,2 +1,5 @@ import './unit-price'; +import './delivery-time'; +import './tax-info'; + export * from './component-init' \ No newline at end of file diff --git a/assets/js/blocks/product-elements/shared/config.js b/assets/js/blocks/product-elements/shared/config.js index 6577cd59b..1f3038630 100644 --- a/assets/js/blocks/product-elements/shared/config.js +++ b/assets/js/blocks/product-elements/shared/config.js @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { Icon, grid } from '@wordpress/icons'; +import { __experimentalGetSpacingClassesAndStyles } from '@wordpress/block-editor'; /** * Internal dependencies @@ -26,6 +27,49 @@ const sharedConfig = { }, supports: { html: false, + color: { + text: true, + background: true, + link: false, + __experimentalSkipSerialization: true, + }, + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontWeight: true, + __experimentalFontStyle: true, + __experimentalSkipSerialization: true, + __experimentalLetterSpacing: true, + }, + ...( typeof __experimentalGetSpacingClassesAndStyles === 'function' && { + spacing: { + margin: true, + padding: true, + }, + } ), + }, + attributes: { + productId: { + type: 'number', + default: 0, + }, + isDescendentOfQueryLoop: { + type: 'boolean', + default: false, + }, + textAlign: { + type: 'string', + default: '', + }, + isDescendentOfSingleProductTemplate: { + type: 'boolean', + default: false, + }, + isDescendentOfSingleProductBlock: { + type: 'boolean', + default: false, + } }, ancestor: [ 'woocommerce/all-products', 'woocommerce/single-product' ], save diff --git a/assets/js/blocks/product-elements/shared/editor.scss b/assets/js/blocks/product-elements/shared/editor.scss new file mode 100644 index 000000000..6f0541fab --- /dev/null +++ b/assets/js/blocks/product-elements/shared/editor.scss @@ -0,0 +1,12 @@ +.wc-gzd-block-components-product-price-label { + display: block; +} + +.wc-gzd-block-components-product-price-label { + &.wc-gzd-block-components-product-price-label--align-center { + text-align: center; + } + &.wc-gzd-block-components-product-price-label--align-right { + text-align: right; + } +} \ No newline at end of file diff --git a/assets/js/blocks/product-elements/shared/formatted-price-label.js b/assets/js/blocks/product-elements/shared/formatted-price-label.js new file mode 100644 index 000000000..e1b54dce6 --- /dev/null +++ b/assets/js/blocks/product-elements/shared/formatted-price-label.js @@ -0,0 +1,75 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; +import { isValidElement } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import './editor.scss'; + +const FormattedPriceLabel = ( { + align, + className, + labelType, + formattedLabel, + labelClassName, + labelStyle, + style, +} ) => { + const wrapperClassName = classNames( + className, + 'wc-gzd-block-components-product-' + labelType, + 'wc-gzd-block-components-product-price-label', + { + [ `wc-gzd-block-components-product-price-label--align-${ align }` ]: align, + } + ); + + let labelComponent = ( + + ); + + if ( formattedLabel ) { + if ( isValidElement( formattedLabel ) ) { + labelComponent = ( + + { formattedLabel } + + ); + } else { + labelComponent = ( + + ); + } + } + + return ( + + { labelComponent } + + ); +}; + +export default FormattedPriceLabel; \ No newline at end of file diff --git a/assets/js/blocks/product-elements/shared/price-label-block.js b/assets/js/blocks/product-elements/shared/price-label-block.js new file mode 100644 index 000000000..30e6a9fdb --- /dev/null +++ b/assets/js/blocks/product-elements/shared/price-label-block.js @@ -0,0 +1,125 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { getCurrencyFromPriceResponse } from '@woocommerce/price-format'; +import { + useInnerBlockLayoutContext, + useProductDataContext, +} from '@woocommerce/shared-context'; +import { __, _x } from '@wordpress/i18n'; +import { withProductDataContext } from '@woocommerce/shared-hocs'; +import { useStyleProps } from '@germanized/base-hooks'; +import FormattedMonetaryAmount from '@germanized/base-components/formatted-monetary-amount'; + +import FormattedPriceLabel from './formatted-price-label'; + +const getPreviewData = ( labelType, productData, isDescendentOfSingleProductTemplate ) => { + const gzdData = productData.hasOwnProperty( 'extensions' ) ? productData.extensions['woocommerce-germanized'] : { + 'unit_price_html': '', + 'unit_prices': { + 'price': 0, + 'regular_price': 0, + 'sale_price': 0 + }, + 'delivery_time_html': '', + 'tax_info_html': '', + }; + + const prices = productData.prices; + const currency = isDescendentOfSingleProductTemplate + ? getCurrencyFromPriceResponse() + : getCurrencyFromPriceResponse( prices ); + + const labelTypeData = labelType.replace( '-', '_' ); + const data = gzdData.hasOwnProperty( labelTypeData + '_html' ) ? gzdData[ labelTypeData + '_html' ] : ''; + let formattedPreview = ''; + + if ( 'unit_price' === labelTypeData ) { + formattedPreview = ( + <> + / { _x( 'kg', 'unit', 'woocommerce-germanized' ) } + + ); + } else if ( 'delivery_time' === labelTypeData ) { + formattedPreview = __( 'Delivery time: 2-3 days', 'preview', 'woocommerce-germanized' ); + } else if ( 'tax_info' === labelTypeData ) { + formattedPreview = __( 'incl. 19 % VAT', 'preview', 'woocommerce-germanized' ); + } else if ( 'shipping_info' === labelTypeData ) { + formattedPreview = __( 'plus shipping costs', 'preview', 'woocommerce-germanized' ); + } + + return { + 'preview': formattedPreview, + 'data': data, + } +}; + +const PriceLabelBlock = ( props ) => { + const { className, textAlign, isDescendentOfSingleProductTemplate, labelType } = props; + const { parentName, parentClassName } = useInnerBlockLayoutContext(); + const { product } = useProductDataContext(); + const styleProps = useStyleProps( props ); + + const isDescendentOfAllProductsBlock = parentName === 'woocommerce/all-products'; + + const wrapperClassName = classnames( + 'wc-gzd-block-components-product-' + labelType, + className, + styleProps.className, + { + [ `${ parentClassName }__product-${ labelType }` ]: parentClassName, + } + ); + + if ( ! product.id && ! isDescendentOfSingleProductTemplate ) { + const productComponent = ( + + ); + + if ( isDescendentOfAllProductsBlock ) { + const allProductsClassName = `wp-block-woocommerce-gzd-product-${ labelType }`; + + return ( +
+ { productComponent } +
+ ); + } + + return productComponent; + } + + const previewData = getPreviewData( labelType, product, isDescendentOfSingleProductTemplate ); + + const productComponent = ( + + ); + + if ( isDescendentOfAllProductsBlock ) { + const allProductsClassName = `wp-block-woocommerce-gzd-product-${ labelType }`; + + return ( +
+ { productComponent } +
+ ); + } + return productComponent; +}; + +export default PriceLabelBlock; diff --git a/assets/js/blocks/product-elements/tax-info/block.js b/assets/js/blocks/product-elements/tax-info/block.js new file mode 100644 index 000000000..ca3fb5164 --- /dev/null +++ b/assets/js/blocks/product-elements/tax-info/block.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { withProductDataContext } from '@woocommerce/shared-hocs'; +import PriceLabelBlock from '../shared/price-label-block'; +export default ( props ) => { + props = { ...props, 'labelType': 'tax-info' }; + + // It is necessary because this block has to support serveral contexts: + // - Inside `All Products Block` -> `withProductDataContext` HOC + // - Inside `Products Block` -> Gutenberg Context + // - Inside `Single Product Template` -> Gutenberg Context + // - Without any parent -> `WithSelector` and `withProductDataContext` HOCs + // For more details, check https://github.com/woocommerce/woocommerce-blocks/pull/8609 + if ( props.isDescendentOfSingleProductTemplate ) { + return ; + } + return withProductDataContext( PriceLabelBlock )( props ); +}; diff --git a/assets/js/blocks/product-elements/tax-info/edit.js b/assets/js/blocks/product-elements/tax-info/edit.js new file mode 100644 index 000000000..81c99099c --- /dev/null +++ b/assets/js/blocks/product-elements/tax-info/edit.js @@ -0,0 +1,65 @@ +/** + * External dependencies + */ +import { + AlignmentToolbar, + BlockControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Block from './block'; +import { useIsDescendentOfSingleProductTemplate } from '../shared/use-is-descendent-of-single-product-template'; +const UnitPriceEdit = ( { + attributes, + setAttributes, + context, +} ) => { + const blockProps = useBlockProps(); + const blockAttrs = { + ...attributes, + ...context, + }; + const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); + + let { isDescendentOfSingleProductTemplate } = + useIsDescendentOfSingleProductTemplate( { isDescendentOfQueryLoop } ); + + if ( isDescendentOfQueryLoop ) { + isDescendentOfSingleProductTemplate = false; + } + + useEffect( + () => + setAttributes( { + isDescendentOfQueryLoop, + isDescendentOfSingleProductTemplate, + } ), + [ + isDescendentOfQueryLoop, + isDescendentOfSingleProductTemplate, + setAttributes, + ] + ); + + return ( + <> + + { + setAttributes( { textAlign } ); + } } + /> + +
+ +
+ + ); +}; + +export default UnitPriceEdit; \ No newline at end of file diff --git a/assets/js/blocks/product-elements/tax-info/frontend.js b/assets/js/blocks/product-elements/tax-info/frontend.js new file mode 100644 index 000000000..60ff2a812 --- /dev/null +++ b/assets/js/blocks/product-elements/tax-info/frontend.js @@ -0,0 +1,5 @@ +/** + * Internal dependencies + */ +import Block from './block'; +export default Block; diff --git a/assets/js/blocks/product-elements/tax-info/index.js b/assets/js/blocks/product-elements/tax-info/index.js new file mode 100644 index 000000000..99ec68f29 --- /dev/null +++ b/assets/js/blocks/product-elements/tax-info/index.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import { currencyDollar, Icon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import sharedConfig from '../shared/config'; +import edit from './edit'; + +const { ancestor, ...configuration } = sharedConfig; + +const blockConfig = { + ...configuration, + apiVersion: 2, + title: __( 'Tax Notice', 'woocommerce-germanized' ), + description: __( 'Inserts the product\'s tax notice.', 'woocommerce-germanized' ), + usesContext: [ 'query', 'queryId', 'postId' ], + icon: { src: }, + + supports: { + ...sharedConfig.supports, + ...( { + __experimentalSelector: + '.wp-block-woocommerce-gzd-product-tax-info .wc-gzd-block-components-product-tax-info', + } ) + }, + edit, +}; + +registerBlockType( 'woocommerce-germanized/product-tax-info', blockConfig ); diff --git a/assets/js/blocks/product-elements/unit-price/block.js b/assets/js/blocks/product-elements/unit-price/block.js index 76b8b5854..55d861239 100644 --- a/assets/js/blocks/product-elements/unit-price/block.js +++ b/assets/js/blocks/product-elements/unit-price/block.js @@ -1,109 +1,12 @@ /** * External dependencies */ -import classnames from 'classnames'; -import { getCurrencyFromPriceResponse } from '@woocommerce/price-format'; -import { - useInnerBlockLayoutContext, - useProductDataContext, -} from '@woocommerce/shared-context'; -import { __, _x } from '@wordpress/i18n'; import { withProductDataContext } from '@woocommerce/shared-hocs'; -import { useStyleProps } from '@germanized/base-hooks'; -import FormattedMonetaryAmount from '@germanized/base-components/formatted-monetary-amount'; - -import ProductUnitPrice from './product-unit-price'; - -export const Block = ( props ) => { - const { className, textAlign, isDescendentOfSingleProductTemplate } = props; - const { parentName, parentClassName } = useInnerBlockLayoutContext(); - const { product } = useProductDataContext(); - const styleProps = useStyleProps( props ); - - const isDescendentOfAllProductsBlock = - parentName === 'woocommerce/all-products'; - - const wrapperClassName = classnames( - 'wc-gzd-block-components-product-unit-price', - className, - styleProps.className, - { - [ `${ parentClassName }__product-unit-price` ]: parentClassName, - } - ); - - if ( ! product.id && ! isDescendentOfSingleProductTemplate ) { - const productPriceComponent = ( - - ); - if ( isDescendentOfAllProductsBlock ) { - return ( -
- { productPriceComponent } -
- ); - } - return productPriceComponent; - } - - const gzdData = product.hasOwnProperty( 'extensions' ) ? product.extensions['woocommerce-germanized'] : { - 'unit_price_html': '', - 'unit_prices': { - 'price': 0, - 'regular_price': 0, - 'sale_price': 0 - } - }; - - const unit_price = gzdData.unit_price_html; - const unit_prices = gzdData.unit_prices; - const prices = product.prices; - const currency = isDescendentOfSingleProductTemplate - ? getCurrencyFromPriceResponse() - : getCurrencyFromPriceResponse( prices ); - - const pricePreview = ( - <> - / { _x( 'kg', 'unit', 'woocommerce-germanized' ) } - - ); - - const isOnSale = unit_prices.price !== unit_prices.regular_price; - const priceClassName = classnames( { - [ `${ parentClassName }__product-unit-price__value` ]: parentClassName, - [ `${ parentClassName }__product-unit-price__value--on-sale` ]: isOnSale, - } ); - - const productPriceComponent = ( - - ); - - if ( isDescendentOfAllProductsBlock ) { - return ( -
- { productPriceComponent } -
- ); - } - return productPriceComponent; -}; +import PriceLabelBlock from '../shared/price-label-block'; export default ( props ) => { + props = { ...props, 'labelType': 'unit-price' }; + // It is necessary because this block has to support serveral contexts: // - Inside `All Products Block` -> `withProductDataContext` HOC // - Inside `Products Block` -> Gutenberg Context @@ -111,7 +14,7 @@ export default ( props ) => { // - Without any parent -> `WithSelector` and `withProductDataContext` HOCs // For more details, check https://github.com/woocommerce/woocommerce-blocks/pull/8609 if ( props.isDescendentOfSingleProductTemplate ) { - return ; + return ; } - return withProductDataContext( Block )( props ); + return withProductDataContext( PriceLabelBlock )( props ); }; diff --git a/assets/js/blocks/product-elements/unit-price/edit.js b/assets/js/blocks/product-elements/unit-price/edit.js index 1837cd267..81c99099c 100644 --- a/assets/js/blocks/product-elements/unit-price/edit.js +++ b/assets/js/blocks/product-elements/unit-price/edit.js @@ -13,7 +13,7 @@ import { useEffect } from '@wordpress/element'; */ import Block from './block'; import { useIsDescendentOfSingleProductTemplate } from '../shared/use-is-descendent-of-single-product-template'; -const PriceEdit = ( { +const UnitPriceEdit = ( { attributes, setAttributes, context, @@ -62,4 +62,4 @@ const PriceEdit = ( { ); }; -export default PriceEdit; \ No newline at end of file +export default UnitPriceEdit; \ No newline at end of file diff --git a/assets/js/blocks/product-elements/unit-price/index.js b/assets/js/blocks/product-elements/unit-price/index.js index d9f517c9f..1eb512660 100644 --- a/assets/js/blocks/product-elements/unit-price/index.js +++ b/assets/js/blocks/product-elements/unit-price/index.js @@ -3,6 +3,7 @@ */ import { registerBlockType } from '@wordpress/blocks'; import { currencyDollar, Icon } from '@wordpress/icons'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -15,62 +16,20 @@ const { ancestor, ...configuration } = sharedConfig; const blockConfig = { ...configuration, apiVersion: 2, - title: 'Unit Price', - description: 'Unit Price', + title: __( 'Unit Price', 'woocommerce-germanized' ), + description: __( 'Inserts the product\'s price per unit.', 'woocommerce-germanized' ), usesContext: [ 'query', 'queryId', 'postId' ], icon: { src: }, - attributes: { - productId: { - type: 'number', - default: 0, - }, - isDescendentOfQueryLoop: { - type: 'boolean', - default: false, - }, - textAlign: { - type: 'string', - default: '', - }, - isDescendentOfSingleProductTemplate: { - type: 'boolean', - default: false, - }, - isDescendentOfSingleProductBlock: { - type: 'boolean', - default: false, - } - }, + supports: { ...sharedConfig.supports, ...( { - color: { - text: true, - background: true, - link: false, - __experimentalSkipSerialization: true, - }, - typography: { - fontSize: true, - lineHeight: true, - __experimentalFontFamily: true, - __experimentalFontWeight: true, - __experimentalFontStyle: true, - __experimentalSkipSerialization: true, - __experimentalLetterSpacing: true, - }, __experimentalSelector: - '.wp-block-woocommerce-product-price .wc-block-components-product-price', - } ), - ...( typeof __experimentalGetSpacingClassesAndStyles === 'function' && { - spacing: { - margin: true, - padding: true, - }, - } ), + '.wp-block-woocommerce-gzd-product-unit-price .wc-gzd-block-components-product-unit-price', + } ) }, edit, }; diff --git a/assets/js/blocks/product-elements/unit-price/product-unit-price/index.js b/assets/js/blocks/product-elements/unit-price/product-unit-price/index.js deleted file mode 100644 index 64e16a2b1..000000000 --- a/assets/js/blocks/product-elements/unit-price/product-unit-price/index.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * External dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import FormattedMonetaryAmount from '@germanized/base-components/formatted-monetary-amount'; -import classNames from 'classnames'; -import { createInterpolateElement, isValidElement } from '@wordpress/element'; -import { formatPrice } from '@woocommerce/price-format'; - -/** - * Internal dependencies - */ -// import './style.scss'; - -const ProductUnitPrice = ( { - align, - className, - formattedPrice, - priceClassName, - priceStyle, - style, - } ) => { - const wrapperClassName = classNames( - className, - 'price', - 'wc-gzd-block-components-product-unit-price', - { - [ `wc-gzd-block-components-product-unit-price--align-${ align }` ]: align, - } - ); - - let priceComponent = ( - - ); - - if ( formattedPrice ) { - if ( isValidElement( formattedPrice ) ) { - priceComponent = ( - - { formattedPrice } - - ); - } else { - priceComponent = ( - - ); - } - } - - return ( - - { priceComponent } - - ); -}; - -export default ProductUnitPrice; \ No newline at end of file diff --git a/assets/js/css/style.scss b/assets/js/css/style.scss new file mode 100644 index 000000000..8579d261c --- /dev/null +++ b/assets/js/css/style.scss @@ -0,0 +1,3 @@ +.wc-gzd-block-grid__product-unit-price { + display: block; +} \ No newline at end of file diff --git a/assets/js/index.js b/assets/js/index.js index e69de29bb..e586c07cf 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -0,0 +1 @@ +import './css/style.scss'; \ No newline at end of file diff --git a/assets/js/static/add-to-cart-variation.js b/assets/js/static/add-to-cart-variation.js index 8226463cb..a03db7c11 100644 --- a/assets/js/static/add-to-cart-variation.js +++ b/assets/js/static/add-to-cart-variation.js @@ -22,7 +22,8 @@ self.$wrapper = self.$product; } - self.replacePrice = self.$wrapper.hasClass( 'bundled_product' ) ? false : wc_gzd_add_to_cart_variation_params.replace_price; + self.isBlockLayout = self.$wrapper.find('.wp-block-woocommerce-product-price').length > 0; + self.replacePrice = self.$wrapper.hasClass( 'bundled_product' ) ? false : wc_gzd_add_to_cart_variation_params.replace_price; $form.on( 'click.wc-gzd-variation-form', '.reset_variations', { GermanizedvariationForm: self }, self.onReset ); $form.on( 'reset_data.wc-gzd-variation-form', { GermanizedvariationForm: self }, self.onReset ); @@ -70,8 +71,18 @@ event.data.GermanizedvariationForm.$form.trigger( 'germanized_reset_data' ); }; - GermanizedVariationForm.prototype.onUpdate = function( event ) { + GermanizedVariationForm.prototype.getElementOrBlock = function( self, element, innerElement ) { + var $wrapper = self.$wrapper; + var blockSearch = '.wp-block-woocommerce-gzd-product-' + element + '[data-is-descendent-of-single-product-template]'; + if ( $wrapper.find( blockSearch ).length > 0 ) { + return $wrapper.find( blockSearch + ' ' + innerElement ); + } else { + return $wrapper.find( innerElement ); + } + }; + + GermanizedVariationForm.prototype.onUpdate = function( event ) { setTimeout( function() { if( typeof event.data === 'undefined' || ! event.data.hasOwnProperty( 'GermanizedvariationForm' ) ) { return; @@ -101,27 +112,28 @@ $priceElement.find( '.price' ).contents().unwrap(); } - $wrapper.find( 'p.delivery-time-info' ).wc_gzd_set_content( variation.delivery_time ); - $wrapper.find( '.defect-description' ).wc_gzd_set_content( variation.defect_description ); - $wrapper.find( '.tax-info' ).wc_gzd_set_content( hasDisplayPrice ? variation.tax_info : '' ); - $wrapper.find( '.deposit-amount' ).wc_gzd_set_content( hasDisplayPrice ? variation.deposit_amount : '' ); - $wrapper.find( '.deposit-packaging-type' ).wc_gzd_set_content( hasDisplayPrice ? variation.deposit_packaging_type : '' ); - $wrapper.find( '.wc-gzd-food-description' ).wc_gzd_set_content( variation.food_description ); - $wrapper.find( '.wc-gzd-nutri-score' ).wc_gzd_set_content( variation.nutri_score ); - $wrapper.find( '.wc-gzd-food-distributor' ).wc_gzd_set_content( variation.food_distributor ); - $wrapper.find( '.wc-gzd-food-place-of-origin' ).wc_gzd_set_content( variation.food_place_of_origin ); - $wrapper.find( '.wc-gzd-net-filling-quantity' ).wc_gzd_set_content( variation.net_filling_quantity ); - $wrapper.find( '.wc-gzd-drained-weight' ).wc_gzd_set_content( variation.drained_weight ); - $wrapper.find( '.wc-gzd-alcohol-content' ).wc_gzd_set_content( 'no' === variation.includes_alcohol ? '' : variation.alcohol_content ); - $wrapper.find( '.wc-gzd-nutrients' ).wc_gzd_set_content( variation.nutrients ); - $wrapper.find( '.wc-gzd-nutrients-heading' ).wc_gzd_set_content( variation.nutrients_heading ); - $wrapper.find( '.wc-gzd-ingredients' ).wc_gzd_set_content( variation.ingredients ); - $wrapper.find( '.wc-gzd-ingredients-heading' ).wc_gzd_set_content( variation.ingredients_heading ); - $wrapper.find( '.wc-gzd-allergenic' ).wc_gzd_set_content( variation.allergenic ); - $wrapper.find( '.wc-gzd-allergenic-heading' ).wc_gzd_set_content( variation.allergenic_heading ); - $wrapper.find( '.shipping-costs-info' ).wc_gzd_set_content( hasDisplayPrice ? variation.shipping_costs_info : '' ); - $wrapper.find( '.price-unit' ).wc_gzd_set_content( hasDisplayPrice ? variation.unit_price : '' ); - $wrapper.find( '.product-units' ).wc_gzd_set_content( hasDisplayPrice ? variation.product_units : '' ); + form.getElementOrBlock( form, 'delivery-time-info', '.delivery-time-info' ).wc_gzd_set_content( variation.delivery_time ); + form.getElementOrBlock( form, 'defect-description', '.defect-description' ).wc_gzd_set_content( variation.defect_description ); + form.getElementOrBlock( form, 'tax-info', '.tax-info' ).wc_gzd_set_content( hasDisplayPrice ? variation.tax_info : '' ); + form.getElementOrBlock( form, 'deposit-amount', '.deposit-amount' ).wc_gzd_set_content( hasDisplayPrice ? variation.deposit_amount : '' ); + form.getElementOrBlock( form, 'deposit-packaging-type', '.deposit-packaging-type' ).wc_gzd_set_content( hasDisplayPrice ? variation.deposit_packaging_type : '' ); + form.getElementOrBlock( form, 'food-description', '.wc-gzd-food-description' ).wc_gzd_set_content( variation.food_description ); + form.getElementOrBlock( form, 'nutri-score', '.wc-gzd-nutri-score' ).wc_gzd_set_content( variation.nutri_score ); + form.getElementOrBlock( form, 'food-distributor', '.wc-gzd-food-distributor' ).wc_gzd_set_content( variation.food_distributor ); + form.getElementOrBlock( form, 'food-place-of-origin', '.wc-gzd-food-place-of-origin' ).wc_gzd_set_content( variation.food_place_of_origin ); + form.getElementOrBlock( form, 'net-filling-quantity', '.wc-gzd-net-filling-quantity' ).wc_gzd_set_content( variation.net_filling_quantity ); + form.getElementOrBlock( form, 'drained-weight', '.wc-gzd-drained-weight' ).wc_gzd_set_content( variation.drained_weight ); + form.getElementOrBlock( form, 'alcohol-content', '.wc-gzd-alcohol-content' ).wc_gzd_set_content( 'no' === variation.includes_alcohol ? '' : variation.alcohol_content ); + form.getElementOrBlock( form, 'nutrients', '.wc-gzd-nutrients' ).wc_gzd_set_content( variation.nutrients ); + form.getElementOrBlock( form, 'nutrients-heading', '.wc-gzd-nutrients-heading' ).wc_gzd_set_content( variation.nutrients_heading ); + form.getElementOrBlock( form, 'ingredients', '.wc-gzd-ingredients' ).wc_gzd_set_content( variation.ingredients ); + form.getElementOrBlock( form, 'ingredients-heading', '.wc-gzd-ingredients-heading' ).wc_gzd_set_content( variation.ingredients_heading ); + form.getElementOrBlock( form, 'allergenic', '.wc-gzd-allergenic' ).wc_gzd_set_content( variation.allergenic ); + + form.getElementOrBlock( form, 'allergenic-heading', '.wc-gzd-allergenic-heading' ).wc_gzd_set_content( variation.allergenic_heading ); + form.getElementOrBlock( form, 'shipping-costs-info', '.shipping-costs-info' ).wc_gzd_set_content( hasDisplayPrice ? variation.shipping_costs_info : '' ); + form.getElementOrBlock( form, 'unit-price', '.price-unit' ).wc_gzd_set_content( hasDisplayPrice ? variation.unit_price : '' ); + form.getElementOrBlock( form, 'product-units', '.product-units' ).wc_gzd_set_content( hasDisplayPrice ? variation.product_units : '' ); form.$form.trigger( 'germanized_variation_data', variation, $wrapper ); }; diff --git a/assets/js/static/unit-price-observer.js b/assets/js/static/unit-price-observer.js index baecea47f..d79225698 100644 --- a/assets/js/static/unit-price-observer.js +++ b/assets/js/static/unit-price-observer.js @@ -161,10 +161,16 @@ return []; } + var isSingleProductBlock = $price.parents( '.wp-block-woocommerce-product-price[data-is-descendent-of-single-product-template]' ).length > 0; + if ( 'SPAN' === $price[0].tagName ) { return self.$wrapper.find( '.price-unit' ); } else { - return self.$wrapper.find( '.price-unit:not(.wc-gzd-additional-info-placeholder, .wc-gzd-additional-info-loop)' ); + if ( isSingleProductBlock ) { + return self.$wrapper.find( '.wp-block-woocommerce-gzd-product-unit-price[data-is-descendent-of-single-product-template] .price-unit' ); + } else { + return self.$wrapper.find( '.price-unit:not(.wc-gzd-additional-info-placeholder, .wc-gzd-additional-info-loop)' ); + } } }; diff --git a/src/Blocks/Assets.php b/src/Blocks/Assets.php index beeb5c14a..df604abeb 100644 --- a/src/Blocks/Assets.php +++ b/src/Blocks/Assets.php @@ -20,14 +20,23 @@ final class Assets { public function __construct() { add_action( 'init', array( $this, 'register_assets' ) ); add_action( is_admin() ? 'admin_print_footer_scripts' : 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_styles' ), 1000 ); } public function register_assets() { $this->register_script( 'wc-gzd-blocks', $this->get_block_asset_build_path( 'wc-gzd-blocks' ), array( 'wc-blocks' ), false ); $this->register_script( 'wc-gzd-blocks-settings', $this->get_block_asset_build_path( 'wc-gzd-blocks-settings' ), array( 'wc-blocks' ), false ); + + $this->register_style( 'wc-gzd-blocks-style', $this->get_block_asset_build_path( 'wc-gzd-blocks', 'css' ), array(), 'all' ); $this->register_style( 'wc-gzd-blocks-editor-style', $this->get_block_asset_build_path( 'wc-gzd-blocks-editor-style', 'css' ), array( 'wp-edit-blocks' ), 'all' ); } + public function enqueue_frontend_styles() { + if ( wp_style_is( 'wc-blocks-style' ) ) { + wp_enqueue_style( 'wc-gzd-blocks-style' ); + } + } + public function data_exists( $key ) { return array_key_exists( $key, $this->data ); } diff --git a/src/Blocks/BlockTypes/AbstractBlock.php b/src/Blocks/BlockTypes/AbstractBlock.php index 220ff5598..0e94f5f21 100644 --- a/src/Blocks/BlockTypes/AbstractBlock.php +++ b/src/Blocks/BlockTypes/AbstractBlock.php @@ -130,7 +130,7 @@ protected function register_block_type_assets() { */ protected function register_chunk_translations( $chunks ) { foreach ( $chunks as $chunk ) { - $handle = 'wc-germanized-blocks-' . $chunk . '-chunk'; + $handle = 'wc-gzd-blocks-' . $chunk . '-chunk'; $this->assets->register_script( $handle, $this->assets->get_block_asset_build_path( $chunk ), array(), true ); wp_add_inline_script( $this->get_block_type_script( 'handle' ), @@ -263,9 +263,9 @@ protected function get_block_type_render_callback() { */ protected function get_block_type_editor_script( $key = null ) { $script = array( - 'handle' => 'wc-germanized-' . $this->block_name . '-block', + 'handle' => 'wc-gzd-' . $this->block_name . '-block', 'path' => $this->assets->get_block_asset_build_path( $this->block_name ), - 'dependencies' => array( 'wc-blocks' ), + 'dependencies' => array( 'wc-blocks', 'wc-gzd-blocks' ), ); return $key ? $script[ $key ] : $script; } @@ -289,7 +289,7 @@ protected function get_block_type_editor_style() { */ protected function get_block_type_script( $key = null ) { $script = array( - 'handle' => 'wc-germanized-' . $this->block_name . '-block-frontend', + 'handle' => 'wc-gzd-' . $this->block_name . '-block-frontend', 'path' => $this->assets->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => array(), ); @@ -302,9 +302,9 @@ protected function get_block_type_script( $key = null ) { * @return string[]|null */ protected function get_block_type_style() { - $this->assets->register_style( 'wc-germanized-blocks-style-' . $this->block_name, $this->assets->get_block_asset_build_path( $this->block_name, 'css' ), array(), 'all', true ); + $this->assets->register_style( 'wc-gzd-blocks-style-' . $this->block_name, $this->assets->get_block_asset_build_path( $this->block_name, 'css' ), array(), 'all', true ); - return array( 'wc-blocks-style', 'wc-germanized-blocks-style-' . $this->block_name ); + return array( 'wc-blocks-style', 'wc-gzd-blocks-style', 'wc-gzd-blocks-style-' . $this->block_name ); } /** diff --git a/src/Blocks/BlockTypes/AbstractProductElementBlock.php b/src/Blocks/BlockTypes/AbstractProductElementBlock.php new file mode 100644 index 000000000..9f9eaeb5e --- /dev/null +++ b/src/Blocks/BlockTypes/AbstractProductElementBlock.php @@ -0,0 +1,139 @@ +get_label_type(); + + return str_replace( '_', '-', $label_type ); + } + + /** + * Get block supports. Shared with the frontend. + * IMPORTANT: If you change anything here, make sure to update the JS file too. + * + * @return array + */ + protected function get_block_type_supports() { + return array( + 'color' => + array( + 'text' => true, + 'background' => true, + 'link' => false, + ), + 'typography' => + array( + 'fontSize' => true, + '__experimentalFontWeight' => true, + '__experimentalFontStyle' => true, + ), + '__experimentalSelector' => '.wp-block-woocommerce-gzd-product-' . $this->get_label_type_class() . ' .wc-gzd-block-components-product-' . $this->get_label_type_class(), + ); + } + + /** + * Get the frontend style handle for this block type. + * + * @return null + */ + protected function get_block_type_style() { + return null; + } + + /** + * Overwrite parent method to prevent script registration. + * + * It is necessary to register and enqueues assets during the render + * phase because we want to load assets only if the block has the content. + */ + protected function register_block_type_assets() { + return null; + } + + /** + * Register the context. + */ + protected function get_block_type_uses_context() { + return array( 'query', 'queryId', 'postId' ); + } + + protected function get_additional_classes( $attributes ) { + return ''; + } + + /** + * Include and render the block. + * + * @param array $attributes Block attributes. Default empty array. + * @param string $content Block content. Default empty string. + * @param \WP_Block $block Block instance. + * @return string Rendered block type output. + */ + protected function render( $attributes, $content, $block ) { + if ( ! empty( $content ) ) { + parent::register_block_type_assets(); + $this->register_chunk_translations( array( $this->block_name ) ); + return $content; + } + + $post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : ''; + $product = wc_gzd_get_product( $post_id ); + + if ( $product && $product->has_unit() ) { + $styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); + $text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes ); + $margin_styles_and_classes = StyleAttributesUtils::get_margin_class_and_style( $attributes ); + $additional_classes = ( isset( $text_align_styles_and_classes['class'] ) ? $text_align_styles_and_classes['class'] : '' ) . ' ' . $styles_and_classes['classes']; + $additional_classes .= ' ' . $this->get_additional_classes( $attributes ); + $html = $this->get_label_content( $product ); + + if ( ! $html && $product->is_type( 'variable' ) ) { + $additional_classes .= ' wc-gzd-additional-info-placeholder'; + } + + return sprintf( + '
+ %6$s +
', + esc_attr( $this->get_label_type_class() ), + esc_attr( isset( $margin_styles_and_classes['class'] ) ? $margin_styles_and_classes['class'] : '' ), + esc_attr( isset( $margin_styles_and_classes['style'] ) ? $margin_styles_and_classes['style'] : '' ), + esc_attr( trim( $additional_classes ) ), + esc_attr( isset( $styles_and_classes['styles'] ) ? $styles_and_classes['styles'] : '' ), + wp_kses_post( $this->get_label_content( $product ) ) + ); + } + } +} diff --git a/src/Blocks/BlockTypes/ProductDeliveryTime.php b/src/Blocks/BlockTypes/ProductDeliveryTime.php new file mode 100644 index 000000000..88c4185cf --- /dev/null +++ b/src/Blocks/BlockTypes/ProductDeliveryTime.php @@ -0,0 +1,30 @@ +get_delivery_time_html(); + } +} diff --git a/src/Blocks/BlockTypes/ProductTaxInfo.php b/src/Blocks/BlockTypes/ProductTaxInfo.php new file mode 100644 index 000000000..3a8c93c8d --- /dev/null +++ b/src/Blocks/BlockTypes/ProductTaxInfo.php @@ -0,0 +1,40 @@ +get_tax_info(); + + if ( ! $html && wc_gzd_is_small_business() ) { + $html = wc_gzd_get_small_business_product_notice(); + } + + if ( false === $html ) { + $html = ''; + } + + return $html; + } +} diff --git a/src/Blocks/BlockTypes/ProductUnitPrice.php b/src/Blocks/BlockTypes/ProductUnitPrice.php index bb1e0d4d5..f2cb2f266 100644 --- a/src/Blocks/BlockTypes/ProductUnitPrice.php +++ b/src/Blocks/BlockTypes/ProductUnitPrice.php @@ -6,13 +6,7 @@ /** * ProductPrice class. */ -class ProductUnitPrice extends AbstractBlock { - /** - * Block namespace. - * - * @var string - */ - protected $namespace = 'woocommerce-germanized'; +class ProductUnitPrice extends AbstractProductElementBlock { /** * Block name. @@ -21,94 +15,20 @@ class ProductUnitPrice extends AbstractBlock { */ protected $block_name = 'product-unit-price'; - /** - * API version name. - * - * @var string - */ - protected $api_version = '2'; - - /** - * Get block supports. Shared with the frontend. - * IMPORTANT: If you change anything here, make sure to update the JS file too. - * - * @return array - */ - protected function get_block_type_supports() { - return array( - 'color' => - array( - 'text' => true, - 'background' => true, - 'link' => false, - ), - 'typography' => - array( - 'fontSize' => true, - '__experimentalFontWeight' => true, - '__experimentalFontStyle' => true, - ), - '__experimentalSelector' => '.wp-block-woocommerce-product-price .wc-block-components-product-price', - ); + protected function get_label_type() { + return 'unit_price'; } /** - * Get the frontend style handle for this block type. + * @param \WC_GZD_Product $product * - * @return null + * @return string */ - protected function get_block_type_style() { - return null; + protected function get_label_content( $product ) { + return $product->has_unit() ? $product->get_unit_price_html() : ''; } - /** - * Overwrite parent method to prevent script registration. - * - * It is necessary to register and enqueues assets during the render - * phase because we want to load assets only if the block has the content. - */ - protected function register_block_type_assets() { - return null; - } - - /** - * Register the context. - */ - protected function get_block_type_uses_context() { - return array( 'query', 'queryId', 'postId' ); - } - - /** - * Include and render the block. - * - * @param array $attributes Block attributes. Default empty array. - * @param string $content Block content. Default empty string. - * @param \WP_Block $block Block instance. - * @return string Rendered block type output. - */ - protected function render( $attributes, $content, $block ) { - if ( ! empty( $content ) ) { - parent::register_block_type_assets(); - $this->register_chunk_translations( array( $this->block_name ) ); - return $content; - } - - $post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : ''; - $product = wc_gzd_get_product( $post_id ); - - if ( $product->has_unit() ) { - $styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); - $text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes ); - - return sprintf( - '

', - esc_attr( $text_align_styles_and_classes['class'] ?? '' ), - esc_attr( $styles_and_classes['classes'] ), - esc_attr( $styles_and_classes['styles'] ?? '' ), - $product->get_unit_price_html() - ); - } + protected function get_additional_classes( $attributes ) { + return 'price-unit'; } } diff --git a/src/Blocks/BlockTypesController.php b/src/Blocks/BlockTypesController.php index dbd208eed..c58b8e106 100644 --- a/src/Blocks/BlockTypesController.php +++ b/src/Blocks/BlockTypesController.php @@ -43,9 +43,11 @@ public function register_blocks() { */ protected function get_block_types() { $block_types = array( - 'ProductUnitPrice', 'CheckoutCheckboxes', 'CheckoutPhotovoltaicSystemNotice', + 'ProductUnitPrice', + 'ProductDeliveryTime', + 'ProductTaxInfo', ); return $block_types; diff --git a/src/Blocks/Products.php b/src/Blocks/Products.php index ecbf0cb80..273fcd17a 100644 --- a/src/Blocks/Products.php +++ b/src/Blocks/Products.php @@ -142,40 +142,42 @@ private function register_endpoint_data() { $html_formatter = \Automattic\WooCommerce\Blocks\Package::container()->get( \Automattic\WooCommerce\StoreApi\StoreApi::class )->container()->get( ExtendSchema::class )->get_formatter( 'html' ); return array( - 'unit_price_html' => $html_formatter->format( $gzd_product->get_unit_price_html() ), - 'unit_prices' => (object) $this->get_unit_prices( $gzd_product ), - 'unit' => $gzd_product->get_unit(), - 'unit_base' => $gzd_product->get_unit_base(), - 'unit_product' => $gzd_product->get_unit_product(), + 'unit_price_html' => $html_formatter->format( $gzd_product->get_unit_price_html() ), + 'unit_prices' => (object) $this->get_unit_prices( $gzd_product ), + 'unit' => $gzd_product->get_unit(), + 'unit_base' => $gzd_product->get_unit_base(), + 'unit_product' => $gzd_product->get_unit_product(), + 'delivery_time_html' => $html_formatter->format( $gzd_product->get_delivery_time_html() ), + 'tax_info_html' => $html_formatter->format( $gzd_product->get_tax_info() ), ); }, 'schema_callback' => function () { return array( - 'unit' => array( + 'unit' => array( 'description' => __( 'The unit for the unit price.', 'woocommerce-germanized' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'unit_base' => array( + 'unit_base' => array( 'description' => __( 'The unit base.', 'woocommerce-germanized' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'unit_product' => array( + 'unit_product' => array( 'description' => __( 'The unit product.', 'woocommerce-germanized' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'unit_price_html' => array( + 'unit_price_html' => array( 'description' => __( 'Unit price string formatted as HTML.', 'woocommerce-germanized' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'unit_prices' => array( + 'unit_prices' => array( 'description' => __( 'Unit price data provided using the smallest unit of the currency.', 'woocommerce-germanized' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), @@ -221,6 +223,18 @@ private function register_endpoint_data() { ), ), ), + 'delivery_time_html' => array( + 'description' => __( 'Delivery time formatted as HTML.', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'tax_info_html' => array( + 'description' => __( 'Tax notice formatted as HTML.', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), ); }, ) diff --git a/webpack.config.js b/webpack.config.js index 37ce539d9..25dfc9063 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -83,6 +83,7 @@ const entries = { styling: { // Shared blocks code 'wc-gzd-blocks': './assets/js/index.js', + 'wc-gzd-blocks-product-elements': './assets/js/blocks/product-elements/index.js', ...getBlockEntries( '{index,block,frontend}.{t,j}s{,x}' ), }, core: {