Mini Cart as template part (https://github.com/woocommerce/woocommerce-blocks/pull/5025)
* Fix wrong event prefix in doc comment * Make className prop in CartLineItemsTableProps optional * Mini Cart as template part * Remove BlockTemplatePartsController and instead use BlockTemplatesController * Remove old code * Clean up frontend rendering * Update tests * Improve if clause * Fix wrong tests title * Fix wrong variable name * Make sure Mini Cart contents block is unmounted whem mini cart closes or unmounts * Remove unnecessary waitFor * Fix PaymentMethodDataProvider wrong children type * TypeScript fixes * Make comment shorter * Remove test code * Fix contant unmounts of Mini Cart contents block * Fix wrong template_type passed * Set Template part area to 'uncategorized' * Set Template part area to the correct value * Move template dir check outside loop
This commit is contained in:
parent
65233c4e86
commit
bbaa4d8798
|
@ -36,7 +36,7 @@ const setUp = (): void => {
|
|||
const addListeners = (): void => {
|
||||
setUp();
|
||||
|
||||
if ( ! window.wcBlocksStoreCartListeners.count ) {
|
||||
if ( window.wcBlocksStoreCartListeners.count === 0 ) {
|
||||
const removeJQueryAddedToCartEvent = translateJQueryEventToNative(
|
||||
'added_to_cart',
|
||||
`wc-blocks_added_to_cart`
|
||||
|
|
|
@ -60,7 +60,7 @@ export const usePaymentMethodDataContext = (): PaymentMethodDataContextType => {
|
|||
export const PaymentMethodDataProvider = ( {
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactChildren;
|
||||
children: React.ReactNode;
|
||||
} ): JSX.Element => {
|
||||
const {
|
||||
isProcessing: checkoutIsProcessing,
|
||||
|
|
|
@ -53,17 +53,47 @@ const renderBlockInContainers = ( {
|
|||
};
|
||||
el.classList.remove( 'is-loading' );
|
||||
|
||||
render(
|
||||
<BlockErrorBoundary { ...errorBoundaryProps }>
|
||||
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
|
||||
<Block { ...props } attributes={ attributes } />
|
||||
</Suspense>
|
||||
</BlockErrorBoundary>,
|
||||
el
|
||||
);
|
||||
renderBlock( {
|
||||
Block,
|
||||
container: el,
|
||||
props,
|
||||
attributes,
|
||||
errorBoundaryProps,
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a block component in a single `container` node.
|
||||
*
|
||||
* @param {Object} props Render props.
|
||||
* @param {Function} props.Block React component to use as a
|
||||
* replacement.
|
||||
* @param {Node} props.container Container to replace with
|
||||
* the Block component.
|
||||
* @param {Object} [props.attributes] Attributes object for the
|
||||
* block.
|
||||
* @param {Object} [props.props] Props object for the block.
|
||||
* @param {Object} [props.errorBoundaryProps] Props object for the error
|
||||
* boundary.
|
||||
*/
|
||||
export const renderBlock = ( {
|
||||
Block,
|
||||
container,
|
||||
attributes = {},
|
||||
props = {},
|
||||
errorBoundaryProps = {},
|
||||
} ) => {
|
||||
render(
|
||||
<BlockErrorBoundary { ...errorBoundaryProps }>
|
||||
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
|
||||
<Block { ...props } attributes={ attributes } />
|
||||
</Suspense>
|
||||
</BlockErrorBoundary>,
|
||||
container
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the block frontend in the elements matched by the selector which are
|
||||
* outside the wrapper elements.
|
||||
|
@ -141,7 +171,7 @@ const renderBlockInsideWrapper = ( {
|
|||
* Renders the block frontend on page load. If the block is contained inside a
|
||||
* wrapper element that should be excluded from initial load, it adds the
|
||||
* appropriate event listeners to render the block when the
|
||||
* `blocks_render_blocks_frontend` event is triggered.
|
||||
* `wc-blocks_render_blocks_frontend` event is triggered.
|
||||
*
|
||||
* @param {Object} props Render props.
|
||||
* @param {Function} props.Block React component to use as a
|
||||
|
|
|
@ -19,7 +19,7 @@ const placeholderRows = [ ...Array( 3 ) ].map( ( _x, i ) => (
|
|||
interface CartLineItemsTableProps {
|
||||
lineItems: CartResponseItem[];
|
||||
isLoading: boolean;
|
||||
className: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const setRefs = ( lineItems: CartResponseItem[] ) => {
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import {
|
||||
usePaymentMethods,
|
||||
useStoreCart,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { TotalsItem } from '@woocommerce/blocks-checkout';
|
||||
import { CART_URL, CHECKOUT_URL } from '@woocommerce/block-settings';
|
||||
import Button from '@woocommerce/base-components/button';
|
||||
import { PaymentMethodDataProvider } from '@woocommerce/base-context';
|
||||
import { getIconsFromPaymentMethods } from '@woocommerce/base-utils';
|
||||
import PaymentMethodIcons from '@woocommerce/base-components/cart-checkout/payment-method-icons';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CartLineItemsTable from '../cart/cart-line-items-table';
|
||||
|
||||
const PaymentMethodIconsElement = (): JSX.Element => {
|
||||
const { paymentMethods } = usePaymentMethods();
|
||||
return (
|
||||
<PaymentMethodIcons
|
||||
icons={ getIconsFromPaymentMethods( paymentMethods ) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MiniCartContentsBlock = (): JSX.Element => {
|
||||
const { cartItems, cartIsLoading, cartTotals } = useStoreCart();
|
||||
const emptyCartRef = useRef< HTMLDivElement | null >( null );
|
||||
|
||||
const subTotal = getSetting( 'displayCartPricesIncludingTax', false )
|
||||
? parseInt( cartTotals.total_items, 10 ) +
|
||||
parseInt( cartTotals.total_items_tax, 10 )
|
||||
: parseInt( cartTotals.total_items, 10 );
|
||||
|
||||
useEffect( () => {
|
||||
// If the cart has been completely emptied, move focus to empty cart
|
||||
// element.
|
||||
if ( ! cartIsLoading && cartItems.length === 0 ) {
|
||||
if ( emptyCartRef.current instanceof HTMLElement ) {
|
||||
emptyCartRef.current.focus();
|
||||
}
|
||||
}
|
||||
}, [ cartIsLoading, cartItems.length, emptyCartRef ] );
|
||||
|
||||
return ! cartIsLoading && cartItems.length === 0 ? (
|
||||
<div
|
||||
className="wc-block-mini-cart__empty-cart"
|
||||
tabIndex={ -1 }
|
||||
ref={ emptyCartRef }
|
||||
>
|
||||
{ __( 'Cart is empty', 'woo-gutenberg-products-block' ) }
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="wc-block-mini-cart__items">
|
||||
<CartLineItemsTable
|
||||
lineItems={ cartItems }
|
||||
isLoading={ cartIsLoading }
|
||||
/>
|
||||
</div>
|
||||
<div className="wc-block-mini-cart__footer">
|
||||
<TotalsItem
|
||||
className="wc-block-mini-cart__footer-subtotal"
|
||||
currency={ getCurrencyFromPriceResponse( cartTotals ) }
|
||||
label={ __( 'Subtotal', 'woo-gutenberg-products-block' ) }
|
||||
value={ subTotal }
|
||||
description={ __(
|
||||
'Shipping, taxes, and discounts calculated at checkout.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
/>
|
||||
<div className="wc-block-mini-cart__footer-actions">
|
||||
<Button
|
||||
className="wc-block-mini-cart__footer-cart"
|
||||
href={ CART_URL }
|
||||
>
|
||||
{ __( 'View my cart', 'woo-gutenberg-products-block' ) }
|
||||
</Button>
|
||||
<Button
|
||||
className="wc-block-mini-cart__footer-checkout"
|
||||
href={ CHECKOUT_URL }
|
||||
>
|
||||
{ __(
|
||||
'Go to checkout',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</Button>
|
||||
</div>
|
||||
<PaymentMethodDataProvider>
|
||||
<PaymentMethodIconsElement />
|
||||
</PaymentMethodDataProvider>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MiniCartContentsBlock;
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { ReactElement } from 'react';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
|
||||
const Edit = (): ReactElement => {
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<p>Editing the mini cart contents</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Edit;
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, cart } from '@woocommerce/icons';
|
||||
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import edit from './edit';
|
||||
|
||||
const settings = {
|
||||
apiVersion: 2,
|
||||
title: __( 'Mini Cart Contents', 'woo-gutenberg-products-block' ),
|
||||
icon: {
|
||||
src: <Icon srcElement={ cart } />,
|
||||
foreground: '#7f54b3',
|
||||
},
|
||||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
|
||||
description: __(
|
||||
'Display a mini cart widget.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
supports: {
|
||||
align: false,
|
||||
html: false,
|
||||
multiple: false,
|
||||
reusable: false,
|
||||
inserter: false,
|
||||
},
|
||||
attributes: {
|
||||
lock: {
|
||||
type: 'object',
|
||||
default: {
|
||||
remove: true,
|
||||
move: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
example: {
|
||||
attributes: {
|
||||
isPreview: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
isPreview: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
save: false,
|
||||
},
|
||||
},
|
||||
|
||||
edit,
|
||||
|
||||
save() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
registerExperimentalBlockType( 'woocommerce/mini-cart-contents', settings );
|
|
@ -3,69 +3,89 @@
|
|||
*/
|
||||
import classnames from 'classnames';
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { useState, useEffect, useRef } from '@wordpress/element';
|
||||
import {
|
||||
RawHTML,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
unmountComponentAtNode,
|
||||
} from '@wordpress/element';
|
||||
import {
|
||||
renderBlock,
|
||||
translateJQueryEventToNative,
|
||||
getIconsFromPaymentMethods,
|
||||
} from '@woocommerce/base-utils';
|
||||
import {
|
||||
useStoreCart,
|
||||
usePaymentMethods,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import Drawer from '@woocommerce/base-components/drawer';
|
||||
import {
|
||||
formatPrice,
|
||||
getCurrencyFromPriceResponse,
|
||||
} from '@woocommerce/price-format';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { TotalsItem } from '@woocommerce/blocks-checkout';
|
||||
import PaymentMethodIcons from '@woocommerce/base-components/cart-checkout/payment-method-icons';
|
||||
import { CART_URL, CHECKOUT_URL } from '@woocommerce/block-settings';
|
||||
import Button from '@woocommerce/base-components/button';
|
||||
import { PaymentMethodDataProvider } from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CartLineItemsTable from '../cart/cart-line-items-table';
|
||||
import QuantityBadge from './quantity-badge';
|
||||
import MiniCartContentsBlock from '../mini-cart-contents/block';
|
||||
import './style.scss';
|
||||
|
||||
const PaymentMethodIconsElement = (): JSX.Element => {
|
||||
const { paymentMethods } = usePaymentMethods();
|
||||
return (
|
||||
<PaymentMethodIcons
|
||||
icons={ getIconsFromPaymentMethods( paymentMethods ) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
isInitiallyOpen?: boolean;
|
||||
transparentButton: boolean;
|
||||
colorClassNames?: string;
|
||||
style?: Record< string, Record< string, string > >;
|
||||
contents: string;
|
||||
}
|
||||
|
||||
const MiniCartBlock = ( {
|
||||
isInitiallyOpen = false,
|
||||
colorClassNames,
|
||||
style,
|
||||
contents = '',
|
||||
}: Props ): JSX.Element => {
|
||||
const {
|
||||
cartItems,
|
||||
cartItemsCount,
|
||||
cartIsLoading,
|
||||
cartTotals,
|
||||
} = useStoreCart();
|
||||
const { cartItemsCount, cartIsLoading, cartTotals } = useStoreCart();
|
||||
const [ isOpen, setIsOpen ] = useState< boolean >( isInitiallyOpen );
|
||||
const emptyCartRef = useRef< HTMLDivElement | null >( null );
|
||||
// We already rendered the HTML drawer placeholder, so we want to skip the
|
||||
// slide in animation.
|
||||
const [ skipSlideIn, setSkipSlideIn ] = useState< boolean >(
|
||||
isInitiallyOpen
|
||||
);
|
||||
|
||||
const contentsRef = useRef() as React.MutableRefObject< HTMLDivElement >;
|
||||
|
||||
useEffect( () => {
|
||||
if ( contentsRef.current instanceof Element ) {
|
||||
const container = contentsRef.current.querySelector(
|
||||
'.wc-block-mini-cart-contents'
|
||||
);
|
||||
if ( ! container ) {
|
||||
return;
|
||||
}
|
||||
if ( isOpen ) {
|
||||
renderBlock( {
|
||||
Block: MiniCartContentsBlock,
|
||||
container,
|
||||
} );
|
||||
} else {
|
||||
unmountComponentAtNode( container );
|
||||
}
|
||||
}
|
||||
}, [ isOpen ] );
|
||||
|
||||
useEffect( () => {
|
||||
return () => {
|
||||
const contentsNode = contentsRef.current as unknown;
|
||||
if ( contentsNode instanceof Element ) {
|
||||
const container = contentsNode.querySelector(
|
||||
'.wc-block-mini-cart-contents'
|
||||
);
|
||||
if ( container ) {
|
||||
unmountComponentAtNode( container );
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [] );
|
||||
|
||||
useEffect( () => {
|
||||
const openMiniCart = () => {
|
||||
setSkipSlideIn( false );
|
||||
|
@ -93,16 +113,6 @@ const MiniCartBlock = ( {
|
|||
};
|
||||
}, [] );
|
||||
|
||||
useEffect( () => {
|
||||
// If the cart has been completely emptied, move focus to empty cart
|
||||
// element.
|
||||
if ( isOpen && ! cartIsLoading && cartItems.length === 0 ) {
|
||||
if ( emptyCartRef.current instanceof HTMLElement ) {
|
||||
emptyCartRef.current.focus();
|
||||
}
|
||||
}
|
||||
}, [ isOpen, cartIsLoading, cartItems.length, emptyCartRef ] );
|
||||
|
||||
const subTotal = getSetting( 'displayCartPricesIncludingTax', false )
|
||||
? parseInt( cartTotals.total_items, 10 ) +
|
||||
parseInt( cartTotals.total_items_tax, 10 )
|
||||
|
@ -125,64 +135,6 @@ const MiniCartBlock = ( {
|
|||
color: style?.color?.text,
|
||||
};
|
||||
|
||||
const contents =
|
||||
! cartIsLoading && cartItems.length === 0 ? (
|
||||
<div
|
||||
className="wc-block-mini-cart__empty-cart"
|
||||
tabIndex={ -1 }
|
||||
ref={ emptyCartRef }
|
||||
>
|
||||
{ __( 'Cart is empty', 'woo-gutenberg-products-block' ) }
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="wc-block-mini-cart__items">
|
||||
<CartLineItemsTable
|
||||
lineItems={ cartItems }
|
||||
isLoading={ cartIsLoading }
|
||||
/>
|
||||
</div>
|
||||
<div className="wc-block-mini-cart__footer">
|
||||
<TotalsItem
|
||||
className="wc-block-mini-cart__footer-subtotal"
|
||||
currency={ getCurrencyFromPriceResponse( cartTotals ) }
|
||||
label={ __(
|
||||
'Subtotal',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ subTotal }
|
||||
description={ __(
|
||||
'Shipping, taxes, and discounts calculated at checkout.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
/>
|
||||
<div className="wc-block-mini-cart__footer-actions">
|
||||
<Button
|
||||
className="wc-block-mini-cart__footer-cart"
|
||||
href={ CART_URL }
|
||||
>
|
||||
{ __(
|
||||
'View my cart',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</Button>
|
||||
<Button
|
||||
className="wc-block-mini-cart__footer-checkout"
|
||||
href={ CHECKOUT_URL }
|
||||
>
|
||||
{ __(
|
||||
'Go to checkout',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</Button>
|
||||
</div>
|
||||
<PaymentMethodDataProvider>
|
||||
<PaymentMethodIconsElement />
|
||||
</PaymentMethodDataProvider>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
|
@ -236,7 +188,9 @@ const MiniCartBlock = ( {
|
|||
} }
|
||||
slideIn={ ! skipSlideIn }
|
||||
>
|
||||
{ contents }
|
||||
<div ref={ contentsRef }>
|
||||
<RawHTML>{ contents }</RawHTML>
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -41,6 +41,9 @@ const renderMiniCartFrontend = () => {
|
|||
isInitiallyOpen: el.dataset.isInitiallyOpen === 'true',
|
||||
colorClassNames,
|
||||
style: el.dataset.style ? JSON.parse( el.dataset.style ) : {},
|
||||
contents: el.querySelector(
|
||||
'.wc-block-mini-cart__template-part'
|
||||
)?.innerHTML,
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -23,7 +23,10 @@ import { defaultCartState } from '../../../../data/default-states';
|
|||
|
||||
const MiniCartBlock = ( props ) => (
|
||||
<SlotFillProvider>
|
||||
<Block { ...props } />
|
||||
<Block
|
||||
contents='<div class="wc-block-mini-cart-contents"></div>'
|
||||
{ ...props }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
|
||||
|
@ -47,7 +50,7 @@ const mockFullCart = () => {
|
|||
} );
|
||||
};
|
||||
|
||||
describe( 'Testing cart', () => {
|
||||
describe( 'Testing Mini Cart', () => {
|
||||
beforeEach( async () => {
|
||||
mockFullCart();
|
||||
// need to clear the store resolution state between tests.
|
||||
|
@ -93,10 +96,12 @@ describe( 'Testing cart', () => {
|
|||
} );
|
||||
|
||||
await waitForElementToBeRemoved( () =>
|
||||
screen.queryByLabelText( /3 items/i )
|
||||
screen.queryByLabelText( /3 items in cart/i )
|
||||
);
|
||||
await waitFor( () =>
|
||||
expect( screen.getByLabelText( /0 items/i ) ).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByLabelText( /0 items in cart/i )
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
} );
|
||||
|
||||
|
@ -107,16 +112,18 @@ describe( 'Testing cart', () => {
|
|||
|
||||
mockFullCart();
|
||||
// eslint-disable-next-line no-undef
|
||||
const removedFromCartEvent = new Event( 'wc-blocks_added_to_cart' );
|
||||
const addedToCartEvent = new Event( 'wc-blocks_added_to_cart' );
|
||||
act( () => {
|
||||
document.body.dispatchEvent( removedFromCartEvent );
|
||||
document.body.dispatchEvent( addedToCartEvent );
|
||||
} );
|
||||
|
||||
await waitForElementToBeRemoved( () =>
|
||||
screen.queryByLabelText( /0 items/i )
|
||||
screen.queryByLabelText( /0 items in cart/i )
|
||||
);
|
||||
await waitFor( () =>
|
||||
expect( screen.getAllByLabelText( /3 items/i ).length > 0 )
|
||||
expect(
|
||||
screen.getByLabelText( /3 items in cart/i )
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -50,6 +50,10 @@ const blocks = {
|
|||
customDir: 'cart-checkout/mini-cart',
|
||||
isExperimental: true,
|
||||
},
|
||||
'mini-cart-contents': {
|
||||
customDir: 'cart-checkout/mini-cart-contents',
|
||||
isExperimental: true,
|
||||
},
|
||||
'single-product': {
|
||||
isExperimental: true,
|
||||
},
|
||||
|
|
|
@ -17,6 +17,13 @@ class BlockTemplatesController {
|
|||
*/
|
||||
private $templates_directory;
|
||||
|
||||
/**
|
||||
* Holds the path for the directory where the block template parts will be kept.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $template_parts_directory;
|
||||
|
||||
/**
|
||||
* Directory name of the block template directory.
|
||||
*
|
||||
|
@ -24,11 +31,19 @@ class BlockTemplatesController {
|
|||
*/
|
||||
const TEMPLATES_DIR_NAME = 'block-templates';
|
||||
|
||||
/**
|
||||
* Directory name of the block template parts directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TEMPLATE_PARTS_DIR_NAME = 'block-template-parts';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->templates_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATES_DIR_NAME;
|
||||
$this->templates_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATES_DIR_NAME;
|
||||
$this->template_parts_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATE_PARTS_DIR_NAME;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
|
@ -116,11 +131,11 @@ class BlockTemplatesController {
|
|||
list( , $slug ) = $template_name_parts;
|
||||
|
||||
// If this blocks template doesn't exist then we should just skip the function and let Gutenberg handle it.
|
||||
if ( ! $this->block_template_is_available( $slug ) ) {
|
||||
if ( ! $this->block_template_is_available( $slug, $template_type ) ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$available_templates = $this->get_block_templates( array( $slug ) );
|
||||
$available_templates = $this->get_block_templates( array( $slug ), $template_type );
|
||||
return ( is_array( $available_templates ) && count( $available_templates ) > 0 )
|
||||
? BlockTemplateUtils::gutenberg_build_template_result_from_file( $available_templates[0], $available_templates[0]->type )
|
||||
: $template;
|
||||
|
@ -135,13 +150,13 @@ class BlockTemplatesController {
|
|||
* @return array
|
||||
*/
|
||||
public function add_block_templates( $query_result, $query, $template_type ) {
|
||||
if ( ! function_exists( 'gutenberg_supports_block_templates' ) || ! gutenberg_supports_block_templates() || 'wp_template' !== $template_type ) {
|
||||
if ( ! function_exists( 'gutenberg_supports_block_templates' ) || ! gutenberg_supports_block_templates() ) {
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
|
||||
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
|
||||
$template_files = $this->get_block_templates( $slugs );
|
||||
$template_files = $this->get_block_templates( $slugs, $template_type );
|
||||
|
||||
// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
|
||||
foreach ( $template_files as $template_file ) {
|
||||
|
@ -172,7 +187,7 @@ class BlockTemplatesController {
|
|||
// It would be custom if the template was modified in the editor, so if it's not custom we can load it from
|
||||
// the filesystem.
|
||||
if ( 'custom' !== $template_file->source ) {
|
||||
$template = BlockTemplateUtils::gutenberg_build_template_result_from_file( $template_file, 'wp_template' );
|
||||
$template = BlockTemplateUtils::gutenberg_build_template_result_from_file( $template_file, $template_type );
|
||||
} else {
|
||||
$template_file->title = BlockTemplateUtils::convert_slug_to_title( $template_file->slug );
|
||||
$query_result[] = $template_file;
|
||||
|
@ -242,12 +257,13 @@ class BlockTemplatesController {
|
|||
* Gets the templates saved in the database.
|
||||
*
|
||||
* @param array $slugs An array of slugs to retrieve templates for.
|
||||
* @param array $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return int[]|\WP_Post[] An array of found templates.
|
||||
*/
|
||||
public function get_block_templates_from_db( $slugs = array() ) {
|
||||
public function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
|
||||
$check_query_args = array(
|
||||
'post_type' => 'wp_template',
|
||||
'post_type' => $template_type,
|
||||
'posts_per_page' => -1,
|
||||
'no_found_rows' => true,
|
||||
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
|
@ -278,16 +294,25 @@ class BlockTemplatesController {
|
|||
*
|
||||
* @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned.
|
||||
* @param array $already_found_templates Templates that have already been found, these are customised templates that are loaded from the database.
|
||||
* @param array $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return array Templates from the WooCommerce blocks plugin directory.
|
||||
*/
|
||||
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates ) {
|
||||
$template_files = BlockTemplateUtils::gutenberg_get_template_paths( $this->templates_directory );
|
||||
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates, $template_type = 'wp_template' ) {
|
||||
$directory = $this->get_templates_directory( $template_type );
|
||||
$template_files = BlockTemplateUtils::gutenberg_get_template_paths( $directory );
|
||||
$templates = array();
|
||||
|
||||
if ( 'wp_template_part' === $template_type ) {
|
||||
$dir_name = self::TEMPLATE_PARTS_DIR_NAME;
|
||||
} else {
|
||||
$dir_name = self::TEMPLATES_DIR_NAME;
|
||||
}
|
||||
|
||||
foreach ( $template_files as $template_file ) {
|
||||
$template_slug = substr(
|
||||
$template_file,
|
||||
strpos( $template_file, self::TEMPLATES_DIR_NAME . DIRECTORY_SEPARATOR ) + 1 + strlen( self::TEMPLATES_DIR_NAME ),
|
||||
strpos( $template_file, $dir_name . DIRECTORY_SEPARATOR ) + 1 + strlen( $dir_name ),
|
||||
-5
|
||||
);
|
||||
|
||||
|
@ -318,7 +343,7 @@ class BlockTemplatesController {
|
|||
'slug' => $template_slug,
|
||||
'id' => 'woocommerce//' . $template_slug,
|
||||
'path' => $template_file,
|
||||
'type' => 'wp_template',
|
||||
'type' => $template_type,
|
||||
'theme' => 'woocommerce',
|
||||
'source' => 'woocommerce',
|
||||
'title' => BlockTemplateUtils::convert_slug_to_title( $template_slug ),
|
||||
|
@ -334,14 +359,31 @@ class BlockTemplatesController {
|
|||
* Get and build the block template objects from the block template files.
|
||||
*
|
||||
* @param array $slugs An array of slugs to retrieve templates for.
|
||||
* @param array $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_block_templates( $slugs = array() ) {
|
||||
$templates_from_db = $this->get_block_templates_from_db( $slugs );
|
||||
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db );
|
||||
public function get_block_templates( $slugs = array(), $template_type = 'wp_template' ) {
|
||||
$templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type );
|
||||
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db, $template_type );
|
||||
return array_merge( $templates_from_db, $templates_from_woo );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the directory where templates of a specific template type can be found.
|
||||
*
|
||||
* @param array $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_templates_directory( $template_type = 'wp_template' ) {
|
||||
if ( 'wp_template_part' === $template_type ) {
|
||||
return $this->template_parts_directory;
|
||||
}
|
||||
return $this->templates_directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the theme has a template. So we know if to load our own in or not.
|
||||
*
|
||||
|
@ -357,16 +399,19 @@ class BlockTemplatesController {
|
|||
* Checks whether a block template with that name exists in Woo Blocks
|
||||
*
|
||||
* @param string $template_name Template to check.
|
||||
* @param array $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function block_template_is_available( $template_name ) {
|
||||
public function block_template_is_available( $template_name, $template_type = 'wp_template' ) {
|
||||
if ( ! $template_name ) {
|
||||
return false;
|
||||
}
|
||||
$directory = $this->get_templates_directory( $template_type ) . '/' . $template_name . '.html';
|
||||
|
||||
return is_readable(
|
||||
$this->templates_directory . '/' . $template_name . '.html'
|
||||
) || $this->get_block_templates( array( $template_name ) );
|
||||
$directory
|
||||
) || $this->get_block_templates( array( $template_name ), $template_type );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -302,6 +302,13 @@ class MiniCart extends AbstractBlock {
|
|||
</div>';
|
||||
}
|
||||
|
||||
$part = 'mini-cart';
|
||||
$template_part = gutenberg_get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
|
||||
$template_part_contents = '';
|
||||
if ( $template_part && ! empty( $template_part->content ) ) {
|
||||
$template_part_contents = do_blocks( $template_part->content );
|
||||
}
|
||||
|
||||
return '<div class="' . $wrapper_classes . '">
|
||||
<button class="wc-block-mini-cart__button ' . $classes . '" aria-label="' . esc_attr( $aria_label ) . '" style="' . $style . '">' . $button_html . '</button>
|
||||
<div class="wc-block-mini-cart__drawer is-loading is-mobile wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
|
||||
|
@ -311,81 +318,13 @@ class MiniCart extends AbstractBlock {
|
|||
<div class="components-modal__header-heading-container">
|
||||
<h1 id="components-modal-header-1" class="components-modal__header-heading">' . wp_kses_post( $title ) . '</h1>
|
||||
</div>
|
||||
</div>'
|
||||
. $this->get_cart_contents_markup( $cart_contents ) .
|
||||
'</div>
|
||||
</div>
|
||||
<div class="wc-block-mini-cart__template-part">'
|
||||
. $template_part_contents .
|
||||
'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the markup of the Cart contents.
|
||||
*
|
||||
* @param array $cart_contents Array of contents in the cart.
|
||||
*
|
||||
* @return string The HTML markup.
|
||||
*/
|
||||
protected function get_cart_contents_markup( $cart_contents ) {
|
||||
// Force mobile styles.
|
||||
return '<table class="wc-block-cart-items">
|
||||
<thead>
|
||||
<tr class="wc-block-cart-items__header">
|
||||
<th class="wc-block-cart-items__header-image"><span /></th>
|
||||
<th class="wc-block-cart-items__header-product"><span /></th>
|
||||
<th class="wc-block-cart-items__header-total"><span /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>' . implode( array_map( array( $this, 'get_cart_item_markup' ), $cart_contents ) ) . '</tbody>
|
||||
</table>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the skeleton of a Cart item.
|
||||
*
|
||||
* @return string The skeleton HTML markup.
|
||||
*/
|
||||
protected function get_cart_item_markup() {
|
||||
return '<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-cart-item__quantity">
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
<button class="wc-block-cart-item__remove-link"></button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-cart-item__total-price-and-sale-badge-wrapper">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supports array for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string;
|
||||
*/
|
||||
protected function get_block_type_supports() {
|
||||
return array_merge(
|
||||
parent::get_block_type_supports(),
|
||||
array(
|
||||
'html' => false,
|
||||
'multiple' => false,
|
||||
'color' => true,
|
||||
'__experimentalSelector' => '.wc-block-mini-cart__button, .wc-block-mini-cart__badge',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
|
||||
|
||||
/**
|
||||
* Mini Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MiniCartContents extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-contents';
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
*
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
// The frontend script is a dependency of the Mini Cart block so it's
|
||||
// already lazy-loaded.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the markup for the Mini Cart contents block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
*
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
if ( is_admin() || WC()->is_rest_api_request() ) {
|
||||
// In the editor we will display the placeholder, so no need to
|
||||
// print the markup.
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div class="wc-block-mini-cart-contents"></div>';
|
||||
}
|
||||
}
|
|
@ -185,6 +185,7 @@ final class BlockTypesController {
|
|||
if ( Package::feature()->is_experimental_build() ) {
|
||||
$block_types[] = 'SingleProduct';
|
||||
$block_types[] = 'MiniCart';
|
||||
$block_types[] = 'MiniCartContents';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -109,6 +109,12 @@ class BlockTemplateUtils {
|
|||
$template->has_theme_file = $has_theme_file;
|
||||
$template->is_custom = true;
|
||||
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
|
||||
if ( 'wp_template_part' === $post->post_type ) {
|
||||
$type_terms = get_the_terms( $post, 'wp_template_part_area' );
|
||||
if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
|
||||
$template->area = $type_terms[0]->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
@ -137,6 +143,13 @@ class BlockTemplateUtils {
|
|||
$template->has_theme_file = true;
|
||||
$template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
|
||||
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
|
||||
if ( 'wp_template_part' === $template_type ) {
|
||||
if ( 'mini-cart' === $template_file->slug ) {
|
||||
$template->area = 'mini-cart';
|
||||
} else {
|
||||
$template->area = 'uncategorized';
|
||||
}
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<!-- wp:woocommerce/mini-cart-contents /-->
|
Loading…
Reference in New Issue