khaihihi
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls } from '@wordpress/editor';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconAllReviews } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EditorContainerBlock from '../editor-container-block.js';
|
||||
import NoReviewsPlaceholder from './no-reviews-placeholder.js';
|
||||
import {
|
||||
getSharedReviewContentControls,
|
||||
getSharedReviewListControls,
|
||||
} from '../edit-utils.js';
|
||||
|
||||
/**
|
||||
* Component to handle edit mode of "All Reviews".
|
||||
*/
|
||||
const AllReviewsEditor = ( { attributes, setAttributes } ) => {
|
||||
const getInspectorControls = () => {
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody
|
||||
title={ __( 'Content', 'woocommerce' ) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Product name',
|
||||
'woocommerce'
|
||||
) }
|
||||
checked={ attributes.showProductName }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showProductName: ! attributes.showProductName,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
{ getSharedReviewContentControls(
|
||||
attributes,
|
||||
setAttributes
|
||||
) }
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'List Settings',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ getSharedReviewListControls( attributes, setAttributes ) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ getInspectorControls() }
|
||||
<EditorContainerBlock
|
||||
attributes={ attributes }
|
||||
className="wc-block-all-reviews"
|
||||
icon={ <IconAllReviews className="block-editor-block-icon" /> }
|
||||
name={ __( 'All Reviews', 'woocommerce' ) }
|
||||
noReviewsPlaceholder={ NoReviewsPlaceholder }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
AllReviewsEditor.propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
/**
|
||||
* The register block name.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A callback to update attributes.
|
||||
*/
|
||||
setAttributes: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AllReviewsEditor;
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { IconAllReviews } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import '../editor.scss';
|
||||
import Editor from './edit';
|
||||
import sharedAttributes from '../attributes';
|
||||
import save from '../save.js';
|
||||
import { example } from '../example';
|
||||
|
||||
/**
|
||||
* Register and run the "All Reviews" block.
|
||||
*/
|
||||
registerBlockType( 'woocommerce/all-reviews', {
|
||||
title: __( 'All Reviews', 'woocommerce' ),
|
||||
icon: {
|
||||
src: <IconAllReviews />,
|
||||
foreground: '#96588a',
|
||||
},
|
||||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woocommerce' ) ],
|
||||
description: __(
|
||||
'Shows a list of all product reviews.',
|
||||
'woocommerce'
|
||||
),
|
||||
example: {
|
||||
...example,
|
||||
attributes: {
|
||||
...example.attributes,
|
||||
showProductName: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
...sharedAttributes,
|
||||
/**
|
||||
* Show the product name.
|
||||
*/
|
||||
showProductName: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and manages the block.
|
||||
*/
|
||||
edit( props ) {
|
||||
return <Editor { ...props } />;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the props to post content.
|
||||
*/
|
||||
save,
|
||||
} );
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Placeholder } from '@wordpress/components';
|
||||
import { IconAllReviews } from '@woocommerce/block-components/icons';
|
||||
|
||||
const NoCategoryReviewsPlaceholder = () => {
|
||||
return (
|
||||
<Placeholder
|
||||
className="wc-block-all-reviews"
|
||||
icon={ <IconAllReviews className="block-editor-block-icon" /> }
|
||||
label={ __( 'All Reviews', 'woocommerce' ) }
|
||||
>
|
||||
{ __(
|
||||
'This block shows a list of all product reviews. Your store does not have any reviews yet, but they will show up here when it does.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoCategoryReviewsPlaceholder;
|
||||
@@ -0,0 +1,102 @@
|
||||
export default {
|
||||
/**
|
||||
* Toggle for edit mode in the block preview.
|
||||
*/
|
||||
editMode: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether to display the reviewer or product image.
|
||||
*/
|
||||
imageType: {
|
||||
type: 'string',
|
||||
default: 'reviewer',
|
||||
},
|
||||
|
||||
/**
|
||||
* Order to use for the reviews listing.
|
||||
*/
|
||||
orderby: {
|
||||
type: 'string',
|
||||
default: 'most-recent',
|
||||
},
|
||||
|
||||
/**
|
||||
* Number of reviews to add when clicking on load more.
|
||||
*/
|
||||
reviewsOnLoadMore: {
|
||||
type: 'number',
|
||||
default: 10,
|
||||
},
|
||||
|
||||
/**
|
||||
* Number of reviews to display on page load.
|
||||
*/
|
||||
reviewsOnPageLoad: {
|
||||
type: 'number',
|
||||
default: 10,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the load more button.
|
||||
*/
|
||||
showLoadMore: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the order by selector.
|
||||
*/
|
||||
showOrderby: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the review date.
|
||||
*/
|
||||
showReviewDate: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the reviewer name.
|
||||
*/
|
||||
showReviewerName: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the review image..
|
||||
*/
|
||||
showReviewImage: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the product rating.
|
||||
*/
|
||||
showReviewRating: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the product content.
|
||||
*/
|
||||
showReviewContent: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
previewReviews: {
|
||||
type: 'array',
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Fragment, RawHTML } from '@wordpress/element';
|
||||
import { escapeHTML } from '@wordpress/escape-html';
|
||||
import {
|
||||
Notice,
|
||||
ToggleControl,
|
||||
Toolbar,
|
||||
RangeControl,
|
||||
SelectControl,
|
||||
} from '@wordpress/components';
|
||||
import { BlockControls } from '@wordpress/editor';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import {
|
||||
ENABLE_REVIEW_RATING,
|
||||
SHOW_AVATARS,
|
||||
} from '@woocommerce/block-settings';
|
||||
import ToggleButtonControl from '@woocommerce/block-components/toggle-button-control';
|
||||
|
||||
export const getBlockControls = ( editMode, setAttributes ) => (
|
||||
<BlockControls>
|
||||
<Toolbar
|
||||
controls={ [
|
||||
{
|
||||
icon: 'edit',
|
||||
title: __( 'Edit', 'woocommerce' ),
|
||||
onClick: () => setAttributes( { editMode: ! editMode } ),
|
||||
isActive: editMode,
|
||||
},
|
||||
] }
|
||||
/>
|
||||
</BlockControls>
|
||||
);
|
||||
|
||||
export const getSharedReviewContentControls = ( attributes, setAttributes ) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<ToggleControl
|
||||
label={ __( 'Product rating', 'woocommerce' ) }
|
||||
checked={ attributes.showReviewRating }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showReviewRating: ! attributes.showReviewRating,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
{ attributes.showReviewRating && ! ENABLE_REVIEW_RATING && (
|
||||
<Notice
|
||||
className="wc-block-reviews__notice"
|
||||
isDismissible={ false }
|
||||
>
|
||||
<RawHTML>
|
||||
{ sprintf(
|
||||
escapeHTML(
|
||||
/* translators: A notice that links to WooCommerce settings. */
|
||||
__(
|
||||
'Product rating is disabled in your %sstore settings%s.',
|
||||
'woocommerce'
|
||||
)
|
||||
),
|
||||
`<a href="${ getAdminLink(
|
||||
'admin.php?page=wc-settings&tab=products'
|
||||
) }" target="_blank">`,
|
||||
'</a>'
|
||||
) }
|
||||
</RawHTML>
|
||||
</Notice>
|
||||
) }
|
||||
<ToggleControl
|
||||
label={ __( 'Reviewer name', 'woocommerce' ) }
|
||||
checked={ attributes.showReviewerName }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showReviewerName: ! attributes.showReviewerName,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Image', 'woocommerce' ) }
|
||||
checked={ attributes.showReviewImage }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showReviewImage: ! attributes.showReviewImage,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Review date', 'woocommerce' ) }
|
||||
checked={ attributes.showReviewDate }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showReviewDate: ! attributes.showReviewDate,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Review content', 'woocommerce' ) }
|
||||
checked={ attributes.showReviewContent }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showReviewContent: ! attributes.showReviewContent,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
{ attributes.showReviewImage && (
|
||||
<Fragment>
|
||||
<ToggleButtonControl
|
||||
label={ __(
|
||||
'Review image',
|
||||
'woocommerce'
|
||||
) }
|
||||
value={ attributes.imageType }
|
||||
options={ [
|
||||
{
|
||||
label: __(
|
||||
'Reviewer photo',
|
||||
'woocommerce'
|
||||
),
|
||||
value: 'reviewer',
|
||||
},
|
||||
{
|
||||
label: __(
|
||||
'Product',
|
||||
'woocommerce'
|
||||
),
|
||||
value: 'product',
|
||||
},
|
||||
] }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( { imageType: value } )
|
||||
}
|
||||
/>
|
||||
{ attributes.imageType === 'reviewer' && ! SHOW_AVATARS && (
|
||||
<Notice
|
||||
className="wc-block-reviews__notice"
|
||||
isDismissible={ false }
|
||||
>
|
||||
<RawHTML>
|
||||
{ sprintf(
|
||||
escapeHTML(
|
||||
/* translators: A notice that links to WordPress settings. */
|
||||
__(
|
||||
'Reviewer photo is disabled in your %ssite settings%s.',
|
||||
'woocommerce'
|
||||
)
|
||||
),
|
||||
`<a href="${ getAdminLink(
|
||||
'options-discussion.php'
|
||||
) }" target="_blank">`,
|
||||
'</a>'
|
||||
) }
|
||||
</RawHTML>
|
||||
</Notice>
|
||||
) }
|
||||
</Fragment>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const getSharedReviewListControls = ( attributes, setAttributes ) => {
|
||||
const minPerPage = 1;
|
||||
const maxPerPage = 20;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ToggleControl
|
||||
label={ __( 'Order by', 'woocommerce' ) }
|
||||
checked={ attributes.showOrderby }
|
||||
onChange={ () =>
|
||||
setAttributes( { showOrderby: ! attributes.showOrderby } )
|
||||
}
|
||||
/>
|
||||
<SelectControl
|
||||
label={ __(
|
||||
'Order Product Reviews by',
|
||||
'woocommerce'
|
||||
) }
|
||||
value={ attributes.orderby }
|
||||
options={ [
|
||||
{ label: 'Most recent', value: 'most-recent' },
|
||||
{ label: 'Highest Rating', value: 'highest-rating' },
|
||||
{ label: 'Lowest Rating', value: 'lowest-rating' },
|
||||
] }
|
||||
onChange={ ( orderby ) => setAttributes( { orderby } ) }
|
||||
/>
|
||||
<RangeControl
|
||||
label={ __(
|
||||
'Starting Number of Reviews',
|
||||
'woocommerce'
|
||||
) }
|
||||
value={ attributes.reviewsOnPageLoad }
|
||||
onChange={ ( reviewsOnPageLoad ) =>
|
||||
setAttributes( { reviewsOnPageLoad } )
|
||||
}
|
||||
max={ maxPerPage }
|
||||
min={ minPerPage }
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __( 'Load more', 'woocommerce' ) }
|
||||
checked={ attributes.showLoadMore }
|
||||
onChange={ () =>
|
||||
setAttributes( { showLoadMore: ! attributes.showLoadMore } )
|
||||
}
|
||||
/>
|
||||
{ attributes.showLoadMore && (
|
||||
<RangeControl
|
||||
label={ __(
|
||||
'Load More Reviews',
|
||||
'woocommerce'
|
||||
) }
|
||||
value={ attributes.reviewsOnLoadMore }
|
||||
onChange={ ( reviewsOnLoadMore ) =>
|
||||
setAttributes( { reviewsOnLoadMore } )
|
||||
}
|
||||
max={ maxPerPage }
|
||||
min={ minPerPage }
|
||||
/>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import { ENABLE_REVIEW_RATING } from '@woocommerce/block-settings';
|
||||
import ErrorPlaceholder from '@woocommerce/block-components/error-placeholder';
|
||||
import LoadMoreButton from '@woocommerce/base-components/load-more-button';
|
||||
import ReviewList from '@woocommerce/base-components/review-list';
|
||||
import ReviewSortSelect from '@woocommerce/base-components/review-sort-select';
|
||||
import withReviews from '@woocommerce/base-hocs/with-reviews';
|
||||
|
||||
/**
|
||||
* Block rendered in the editor.
|
||||
*/
|
||||
class EditorBlock extends Component {
|
||||
static propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
// from withReviews
|
||||
reviews: PropTypes.array,
|
||||
totalReviews: PropTypes.number,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
attributes,
|
||||
error,
|
||||
isLoading,
|
||||
noReviewsPlaceholder: NoReviewsPlaceholder,
|
||||
reviews,
|
||||
totalReviews,
|
||||
} = this.props;
|
||||
|
||||
if ( error ) {
|
||||
return (
|
||||
<ErrorPlaceholder
|
||||
className="wc-block-featured-product-error"
|
||||
error={ error }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ( reviews.length === 0 && ! isLoading ) {
|
||||
return <NoReviewsPlaceholder attributes={ attributes } />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Disabled>
|
||||
{ attributes.showOrderby && ENABLE_REVIEW_RATING && (
|
||||
<ReviewSortSelect readOnly value={ attributes.orderby } />
|
||||
) }
|
||||
<ReviewList attributes={ attributes } reviews={ reviews } />
|
||||
{ attributes.showLoadMore && totalReviews > reviews.length && (
|
||||
<LoadMoreButton
|
||||
screenReaderLabel={ __(
|
||||
'Load more reviews',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</Disabled>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withReviews( EditorBlock );
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'lodash';
|
||||
import { Placeholder } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EditorBlock from './editor-block.js';
|
||||
import { getBlockClassName, getSortArgs } from './utils.js';
|
||||
|
||||
/**
|
||||
* Container of the block rendered in the editor.
|
||||
*/
|
||||
class EditorContainerBlock extends Component {
|
||||
renderHiddenContentPlaceholder() {
|
||||
const { icon, name } = this.props;
|
||||
|
||||
return (
|
||||
<Placeholder icon={ icon } label={ name }>
|
||||
{ __(
|
||||
'The content for this block is hidden due to block settings.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { attributes, className, noReviewsPlaceholder } = this.props;
|
||||
const {
|
||||
categoryIds,
|
||||
productId,
|
||||
reviewsOnPageLoad,
|
||||
showProductName,
|
||||
showReviewDate,
|
||||
showReviewerName,
|
||||
showReviewContent,
|
||||
showReviewImage,
|
||||
showReviewRating,
|
||||
} = attributes;
|
||||
const { order, orderby } = getSortArgs( attributes.orderby );
|
||||
const isAllContentHidden =
|
||||
! showReviewContent &&
|
||||
! showReviewRating &&
|
||||
! showReviewDate &&
|
||||
! showReviewerName &&
|
||||
! showReviewImage &&
|
||||
! showProductName;
|
||||
|
||||
if ( isAllContentHidden ) {
|
||||
return this.renderHiddenContentPlaceholder();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ getBlockClassName( className, attributes ) }>
|
||||
<EditorBlock
|
||||
attributes={ attributes }
|
||||
categoryIds={ categoryIds }
|
||||
delayFunction={ ( callback ) => debounce( callback, 400 ) }
|
||||
noReviewsPlaceholder={ noReviewsPlaceholder }
|
||||
orderby={ orderby }
|
||||
order={ order }
|
||||
productId={ productId }
|
||||
reviewsToDisplay={ reviewsOnPageLoad }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditorContainerBlock.propTypes = {
|
||||
attributes: PropTypes.object.isRequired,
|
||||
icon: PropTypes.node.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
noReviewsPlaceholder: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default EditorContainerBlock;
|
||||
@@ -0,0 +1,13 @@
|
||||
.wc-block-reviews__selection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.components-base-control {
|
||||
+ .wc-block-reviews__notice {
|
||||
margin: -$gap 0 $gap;
|
||||
}
|
||||
|
||||
&:nth-last-child(2) + .wc-block-reviews__notice {
|
||||
margin: -$gap 0 $gap-small;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { previewReviews } from '@woocommerce/resource-previews';
|
||||
|
||||
export const example = {
|
||||
attributes: {
|
||||
editMode: false,
|
||||
imageType: 'reviewer',
|
||||
orderby: 'most-recent',
|
||||
reviewsOnLoadMore: 10,
|
||||
reviewsOnPageLoad: 10,
|
||||
showLoadMore: true,
|
||||
showOrderby: true,
|
||||
showReviewDate: true,
|
||||
showReviewerName: true,
|
||||
showReviewImage: true,
|
||||
showReviewRating: true,
|
||||
showReviewContent: true,
|
||||
previewReviews,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ENABLE_REVIEW_RATING } from '@woocommerce/block-settings';
|
||||
import LoadMoreButton from '@woocommerce/base-components/load-more-button';
|
||||
import ReviewSortSelect from '@woocommerce/base-components/review-sort-select';
|
||||
import ReviewList from '@woocommerce/base-components/review-list';
|
||||
import withReviews from '@woocommerce/base-hocs/with-reviews';
|
||||
|
||||
/**
|
||||
* Block rendered in the frontend.
|
||||
*/
|
||||
const FrontendBlock = ( {
|
||||
attributes,
|
||||
onAppendReviews,
|
||||
onChangeOrderby,
|
||||
reviews,
|
||||
totalReviews,
|
||||
} ) => {
|
||||
const { orderby } = attributes;
|
||||
|
||||
if ( reviews.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ attributes.showOrderby !== 'false' && ENABLE_REVIEW_RATING && (
|
||||
<ReviewSortSelect
|
||||
defaultValue={ orderby }
|
||||
onChange={ onChangeOrderby }
|
||||
/>
|
||||
) }
|
||||
<ReviewList attributes={ attributes } reviews={ reviews } />
|
||||
{ attributes.showLoadMore !== 'false' &&
|
||||
totalReviews > reviews.length && (
|
||||
<LoadMoreButton
|
||||
onClick={ onAppendReviews }
|
||||
screenReaderLabel={ __(
|
||||
'Load more reviews',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
FrontendBlock.propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
onAppendReviews: PropTypes.func,
|
||||
onChangeArgs: PropTypes.func,
|
||||
// from withReviewsattributes
|
||||
reviews: PropTypes.array,
|
||||
totalReviews: PropTypes.number,
|
||||
};
|
||||
|
||||
export default withReviews( FrontendBlock );
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { speak } from '@wordpress/a11y';
|
||||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getSortArgs } from './utils';
|
||||
import FrontendBlock from './frontend-block';
|
||||
|
||||
/**
|
||||
* Container of the block rendered in the frontend.
|
||||
*/
|
||||
class FrontendContainerBlock extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
const { attributes } = this.props;
|
||||
|
||||
this.state = {
|
||||
orderby: attributes.orderby,
|
||||
reviewsToDisplay: parseInt( attributes.reviewsOnPageLoad, 10 ),
|
||||
};
|
||||
|
||||
this.onAppendReviews = this.onAppendReviews.bind( this );
|
||||
this.onChangeOrderby = this.onChangeOrderby.bind( this );
|
||||
}
|
||||
|
||||
onAppendReviews() {
|
||||
const { attributes } = this.props;
|
||||
const { reviewsToDisplay } = this.state;
|
||||
|
||||
this.setState( {
|
||||
reviewsToDisplay:
|
||||
reviewsToDisplay + parseInt( attributes.reviewsOnLoadMore, 10 ),
|
||||
} );
|
||||
}
|
||||
|
||||
onChangeOrderby( event ) {
|
||||
const { attributes } = this.props;
|
||||
|
||||
this.setState( {
|
||||
orderby: event.target.value,
|
||||
reviewsToDisplay: parseInt( attributes.reviewsOnPageLoad, 10 ),
|
||||
} );
|
||||
}
|
||||
|
||||
onReviewsAppended( { newReviews } ) {
|
||||
speak(
|
||||
sprintf(
|
||||
_n(
|
||||
'%d review loaded.',
|
||||
'%d reviews loaded.',
|
||||
newReviews.length,
|
||||
'woocommerce'
|
||||
),
|
||||
newReviews.length
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onReviewsReplaced() {
|
||||
speak( __( 'Reviews list updated.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
onReviewsLoadError() {
|
||||
speak(
|
||||
__(
|
||||
'There was an error loading the reviews.',
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { attributes } = this.props;
|
||||
const { categoryIds, productId } = attributes;
|
||||
const { reviewsToDisplay } = this.state;
|
||||
const { order, orderby } = getSortArgs( this.state.orderby );
|
||||
|
||||
return (
|
||||
<FrontendBlock
|
||||
attributes={ attributes }
|
||||
categoryIds={ categoryIds }
|
||||
onAppendReviews={ this.onAppendReviews }
|
||||
onChangeOrderby={ this.onChangeOrderby }
|
||||
onReviewsAppended={ this.onReviewsAppended }
|
||||
onReviewsLoadError={ this.onReviewsLoadError }
|
||||
onReviewsReplaced={ this.onReviewsReplaced }
|
||||
order={ order }
|
||||
orderby={ orderby }
|
||||
productId={ productId }
|
||||
reviewsToDisplay={ reviewsToDisplay }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FrontendContainerBlock.propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default FrontendContainerBlock;
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import FrontendContainerBlock from './frontend-container-block.js';
|
||||
import renderFrontend from '../../utils/render-frontend.js';
|
||||
|
||||
const selector = `
|
||||
.wp-block-woocommerce-all-reviews,
|
||||
.wp-block-woocommerce-reviews-by-product,
|
||||
.wp-block-woocommerce-reviews-by-category
|
||||
`;
|
||||
|
||||
const getProps = ( el ) => {
|
||||
return {
|
||||
attributes: {
|
||||
showReviewDate: el.classList.contains( 'has-date' ),
|
||||
showReviewerName: el.classList.contains( 'has-name' ),
|
||||
showReviewImage: el.classList.contains( 'has-image' ),
|
||||
showReviewRating: el.classList.contains( 'has-rating' ),
|
||||
showReviewContent: el.classList.contains( 'has-content' ),
|
||||
showProductName: el.classList.contains( 'has-product-name' ),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
renderFrontend( selector, FrontendContainerBlock, getProps );
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { InspectorControls } from '@wordpress/editor';
|
||||
import {
|
||||
Button,
|
||||
PanelBody,
|
||||
Placeholder,
|
||||
ToggleControl,
|
||||
withSpokenMessages,
|
||||
} from '@wordpress/components';
|
||||
import { SearchListItem } from '@woocommerce/components';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import ProductCategoryControl from '@woocommerce/block-components/product-category-control';
|
||||
import { IconReviewsByCategory } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EditorContainerBlock from '../editor-container-block.js';
|
||||
import NoReviewsPlaceholder from './no-reviews-placeholder.js';
|
||||
import {
|
||||
getBlockControls,
|
||||
getSharedReviewContentControls,
|
||||
getSharedReviewListControls,
|
||||
} from '../edit-utils.js';
|
||||
|
||||
/**
|
||||
* Component to handle edit mode of "Reviews by Category".
|
||||
*/
|
||||
const ReviewsByCategoryEditor = ( {
|
||||
attributes,
|
||||
debouncedSpeak,
|
||||
setAttributes,
|
||||
} ) => {
|
||||
const { editMode, categoryIds } = attributes;
|
||||
|
||||
const renderCategoryControlItem = ( args ) => {
|
||||
const { item, search, depth = 0 } = args;
|
||||
const classes = [ 'woocommerce-product-categories__item' ];
|
||||
if ( search.length ) {
|
||||
classes.push( 'is-searching' );
|
||||
}
|
||||
if ( depth === 0 && item.parent !== 0 ) {
|
||||
classes.push( 'is-skip-level' );
|
||||
}
|
||||
|
||||
const accessibleName = ! item.breadcrumbs.length
|
||||
? item.name
|
||||
: `${ item.breadcrumbs.join( ', ' ) }, ${ item.name }`;
|
||||
|
||||
return (
|
||||
<SearchListItem
|
||||
className={ classes.join( ' ' ) }
|
||||
{ ...args }
|
||||
showCount
|
||||
aria-label={ sprintf(
|
||||
_n(
|
||||
'%s, has %d product',
|
||||
'%s, has %d products',
|
||||
item.count,
|
||||
'woocommerce'
|
||||
),
|
||||
accessibleName,
|
||||
item.count
|
||||
) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getInspectorControls = () => {
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody
|
||||
title={ __( 'Category', 'woocommerce' ) }
|
||||
initialOpen={ false }
|
||||
>
|
||||
<ProductCategoryControl
|
||||
selected={ attributes.categoryIds }
|
||||
onChange={ ( value = [] ) => {
|
||||
const ids = value.map( ( { id } ) => id );
|
||||
setAttributes( { categoryIds: ids } );
|
||||
} }
|
||||
renderItem={ renderCategoryControlItem }
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __( 'Content', 'woocommerce' ) }
|
||||
>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Product name',
|
||||
'woocommerce'
|
||||
) }
|
||||
checked={ attributes.showProductName }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
showProductName: ! attributes.showProductName,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
{ getSharedReviewContentControls(
|
||||
attributes,
|
||||
setAttributes
|
||||
) }
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'List Settings',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ getSharedReviewListControls( attributes, setAttributes ) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEditMode = () => {
|
||||
const onDone = () => {
|
||||
setAttributes( { editMode: false } );
|
||||
debouncedSpeak(
|
||||
__(
|
||||
'Showing Reviews by Category block preview.',
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
icon={
|
||||
<IconReviewsByCategory className="block-editor-block-icon" />
|
||||
}
|
||||
label={ __(
|
||||
'Reviews by Category',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ __(
|
||||
'Show product reviews from specific categories.',
|
||||
'woocommerce'
|
||||
) }
|
||||
<div className="wc-block-reviews__selection">
|
||||
<ProductCategoryControl
|
||||
selected={ attributes.categoryIds }
|
||||
onChange={ ( value = [] ) => {
|
||||
const ids = value.map( ( { id } ) => id );
|
||||
setAttributes( { categoryIds: ids } );
|
||||
} }
|
||||
showReviewCount={ true }
|
||||
/>
|
||||
<Button isDefault onClick={ onDone }>
|
||||
{ __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
if ( ! categoryIds || editMode ) {
|
||||
return renderEditMode();
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ getBlockControls( editMode, setAttributes ) }
|
||||
{ getInspectorControls() }
|
||||
<EditorContainerBlock
|
||||
attributes={ attributes }
|
||||
className="wc-block-reviews-by-category"
|
||||
icon={
|
||||
<IconReviewsByCategory className="block-editor-block-icon" />
|
||||
}
|
||||
name={ __(
|
||||
'Reviews by Category',
|
||||
'woocommerce'
|
||||
) }
|
||||
noReviewsPlaceholder={ NoReviewsPlaceholder }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ReviewsByCategoryEditor.propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
/**
|
||||
* The register block name.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A callback to update attributes.
|
||||
*/
|
||||
setAttributes: PropTypes.func.isRequired,
|
||||
// from withSpokenMessages
|
||||
debouncedSpeak: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withSpokenMessages( ReviewsByCategoryEditor );
|
||||
@@ -0,0 +1,3 @@
|
||||
.wc-block-reviews-by-category__selection {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { IconReviewsByCategory } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import '../editor.scss';
|
||||
import Editor from './edit';
|
||||
import sharedAttributes from '../attributes';
|
||||
import save from '../save.js';
|
||||
import { example } from '../example';
|
||||
|
||||
/**
|
||||
* Register and run the "Reviews by category" block.
|
||||
*/
|
||||
registerBlockType( 'woocommerce/reviews-by-category', {
|
||||
title: __( 'Reviews by Category', 'woocommerce' ),
|
||||
icon: {
|
||||
src: <IconReviewsByCategory />,
|
||||
foreground: '#96588a',
|
||||
},
|
||||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woocommerce' ) ],
|
||||
description: __(
|
||||
'Show product reviews from specific categories.',
|
||||
'woocommerce'
|
||||
),
|
||||
example: {
|
||||
...example,
|
||||
attributes: {
|
||||
...example.attributes,
|
||||
categoryIds: [ 1 ],
|
||||
showProductName: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
...sharedAttributes,
|
||||
/**
|
||||
* The ids of the categories to load reviews for.
|
||||
*/
|
||||
categoryIds: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
/**
|
||||
* Show the product name.
|
||||
*/
|
||||
showProductName: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and manages the block.
|
||||
*/
|
||||
edit( props ) {
|
||||
return <Editor { ...props } />;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the props to post content.
|
||||
*/
|
||||
save,
|
||||
} );
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Placeholder } from '@wordpress/components';
|
||||
import { IconReviewsByCategory } from '@woocommerce/block-components/icons';
|
||||
|
||||
const NoReviewsPlaceholder = () => {
|
||||
return (
|
||||
<Placeholder
|
||||
className="wc-block-reviews-by-category"
|
||||
icon={
|
||||
<IconReviewsByCategory className="block-editor-block-icon" />
|
||||
}
|
||||
label={ __(
|
||||
'Reviews by Category',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ __(
|
||||
'This block lists reviews for products from selected categories. The selected categories do not have any reviews yet, but they will show up here when they do.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoReviewsPlaceholder;
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { InspectorControls } from '@wordpress/editor';
|
||||
import {
|
||||
Button,
|
||||
PanelBody,
|
||||
Placeholder,
|
||||
withSpokenMessages,
|
||||
} from '@wordpress/components';
|
||||
import { SearchListItem } from '@woocommerce/components';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import ProductControl from '@woocommerce/block-components/product-control';
|
||||
import { IconReviewsByProduct } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EditorContainerBlock from '../editor-container-block.js';
|
||||
import NoReviewsPlaceholder from './no-reviews-placeholder.js';
|
||||
import {
|
||||
getBlockControls,
|
||||
getSharedReviewContentControls,
|
||||
getSharedReviewListControls,
|
||||
} from '../edit-utils.js';
|
||||
|
||||
/**
|
||||
* Component to handle edit mode of "Reviews by Product".
|
||||
*/
|
||||
const ReviewsByProductEditor = ( {
|
||||
attributes,
|
||||
debouncedSpeak,
|
||||
setAttributes,
|
||||
} ) => {
|
||||
const { editMode, productId } = attributes;
|
||||
|
||||
const renderProductControlItem = ( args ) => {
|
||||
const { item = 0 } = args;
|
||||
|
||||
return (
|
||||
<SearchListItem
|
||||
{ ...args }
|
||||
countLabel={ sprintf(
|
||||
_n(
|
||||
'%d Review',
|
||||
'%d Reviews',
|
||||
item.review_count,
|
||||
'woocommerce'
|
||||
),
|
||||
item.review_count
|
||||
) }
|
||||
showCount
|
||||
aria-label={ sprintf(
|
||||
_n(
|
||||
'%s, has %d review',
|
||||
'%s, has %d reviews',
|
||||
item.review_count,
|
||||
'woocommerce'
|
||||
),
|
||||
item.name,
|
||||
item.review_count
|
||||
) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getInspectorControls = () => {
|
||||
return (
|
||||
<InspectorControls key="inspector">
|
||||
<PanelBody
|
||||
title={ __( 'Product', 'woocommerce' ) }
|
||||
initialOpen={ false }
|
||||
>
|
||||
<ProductControl
|
||||
selected={ attributes.productId || 0 }
|
||||
onChange={ ( value = [] ) => {
|
||||
const id = value[ 0 ] ? value[ 0 ].id : 0;
|
||||
setAttributes( { productId: id } );
|
||||
} }
|
||||
renderItem={ renderProductControlItem }
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __( 'Content', 'woocommerce' ) }
|
||||
>
|
||||
{ getSharedReviewContentControls(
|
||||
attributes,
|
||||
setAttributes
|
||||
) }
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'List Settings',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ getSharedReviewListControls( attributes, setAttributes ) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEditMode = () => {
|
||||
const onDone = () => {
|
||||
setAttributes( { editMode: false } );
|
||||
debouncedSpeak(
|
||||
__(
|
||||
'Showing Reviews by Product block preview.',
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
icon={
|
||||
<IconReviewsByProduct className="block-editor-block-icon" />
|
||||
}
|
||||
label={ __(
|
||||
'Reviews by Product',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ __(
|
||||
'Show reviews of your product to build trust',
|
||||
'woocommerce'
|
||||
) }
|
||||
<div className="wc-block-reviews__selection">
|
||||
<ProductControl
|
||||
selected={ attributes.productId || 0 }
|
||||
onChange={ ( value = [] ) => {
|
||||
const id = value[ 0 ] ? value[ 0 ].id : 0;
|
||||
setAttributes( { productId: id } );
|
||||
} }
|
||||
queryArgs={ {
|
||||
orderby: 'comment_count',
|
||||
order: 'desc',
|
||||
} }
|
||||
renderItem={ renderProductControlItem }
|
||||
/>
|
||||
<Button isDefault onClick={ onDone }>
|
||||
{ __( 'Done', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
if ( ! productId || editMode ) {
|
||||
return renderEditMode();
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{ getBlockControls( editMode, setAttributes ) }
|
||||
{ getInspectorControls() }
|
||||
<EditorContainerBlock
|
||||
attributes={ attributes }
|
||||
className="wc-block-all-reviews"
|
||||
icon={
|
||||
<IconReviewsByProduct className="block-editor-block-icon" />
|
||||
}
|
||||
name={ __(
|
||||
'Reviews by Product',
|
||||
'woocommerce'
|
||||
) }
|
||||
noReviewsPlaceholder={ NoReviewsPlaceholder }
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
ReviewsByProductEditor.propTypes = {
|
||||
/**
|
||||
* The attributes for this block.
|
||||
*/
|
||||
attributes: PropTypes.object.isRequired,
|
||||
/**
|
||||
* The register block name.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A callback to update attributes.
|
||||
*/
|
||||
setAttributes: PropTypes.func.isRequired,
|
||||
// from withSpokenMessages
|
||||
debouncedSpeak: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withSpokenMessages( ReviewsByProductEditor );
|
||||
@@ -0,0 +1,13 @@
|
||||
.wc-block-reviews-by-product__selection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.components-base-control {
|
||||
+ .wc-block-reviews-by-product__notice {
|
||||
margin: -$gap 0 $gap;
|
||||
}
|
||||
|
||||
&:nth-last-child(2) + .wc-block-reviews-by-product__notice {
|
||||
margin: -$gap 0 $gap-small;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { IconReviewsByProduct } from '@woocommerce/block-components/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import '../editor.scss';
|
||||
import Editor from './edit';
|
||||
import sharedAttributes from '../attributes';
|
||||
import save from '../save.js';
|
||||
import { example } from '../example';
|
||||
|
||||
/**
|
||||
* Register and run the "Reviews by Product" block.
|
||||
*/
|
||||
registerBlockType( 'woocommerce/reviews-by-product', {
|
||||
title: __( 'Reviews by Product', 'woocommerce' ),
|
||||
icon: {
|
||||
src: <IconReviewsByProduct />,
|
||||
foreground: '#96588a',
|
||||
},
|
||||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woocommerce' ) ],
|
||||
description: __(
|
||||
'Show reviews of your product to build trust.',
|
||||
'woocommerce'
|
||||
),
|
||||
example: {
|
||||
...example,
|
||||
attributes: {
|
||||
...example.attributes,
|
||||
productId: 1,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
...sharedAttributes,
|
||||
/**
|
||||
* The id of the product to load reviews for.
|
||||
*/
|
||||
productId: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and manages the block.
|
||||
*/
|
||||
edit( props ) {
|
||||
return <Editor { ...props } />;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the props to post content.
|
||||
*/
|
||||
save,
|
||||
} );
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Placeholder, Spinner } from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
import ErrorPlaceholder from '@woocommerce/block-components/error-placeholder';
|
||||
import { IconReviewsByProduct } from '@woocommerce/block-components/icons';
|
||||
import { withProduct } from '@woocommerce/block-hocs';
|
||||
|
||||
const NoReviewsPlaceholder = ( { error, getProduct, isLoading, product } ) => {
|
||||
const renderApiError = () => (
|
||||
<ErrorPlaceholder
|
||||
className="wc-block-featured-product-error"
|
||||
error={ error }
|
||||
isLoading={ isLoading }
|
||||
onRetry={ getProduct }
|
||||
/>
|
||||
);
|
||||
|
||||
if ( error ) {
|
||||
return renderApiError();
|
||||
}
|
||||
|
||||
const content =
|
||||
! product || isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
sprintf(
|
||||
__(
|
||||
"This block lists reviews for a selected product. %s doesn't have any reviews yet, but they will show up here when it does.",
|
||||
'woocommerce'
|
||||
),
|
||||
product.name
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
className="wc-block-reviews-by-product"
|
||||
icon={
|
||||
<IconReviewsByProduct className="block-editor-block-icon" />
|
||||
}
|
||||
label={ __( 'Reviews by Product', 'woocommerce' ) }
|
||||
>
|
||||
{ content }
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
NoReviewsPlaceholder.propTypes = {
|
||||
// from withProduct
|
||||
error: PropTypes.object,
|
||||
isLoading: PropTypes.bool,
|
||||
product: PropTypes.shape( {
|
||||
name: PropTypes.node,
|
||||
review_count: PropTypes.number,
|
||||
} ),
|
||||
};
|
||||
|
||||
export default withProduct( NoReviewsPlaceholder );
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './editor.scss';
|
||||
import { getBlockClassName } from './utils.js';
|
||||
|
||||
export default ( { attributes } ) => {
|
||||
const {
|
||||
categoryIds,
|
||||
imageType,
|
||||
orderby,
|
||||
productId,
|
||||
reviewsOnPageLoad,
|
||||
reviewsOnLoadMore,
|
||||
showLoadMore,
|
||||
showOrderby,
|
||||
} = attributes;
|
||||
|
||||
const data = {
|
||||
'data-image-type': imageType,
|
||||
'data-orderby': orderby,
|
||||
'data-reviews-on-page-load': reviewsOnPageLoad,
|
||||
'data-reviews-on-load-more': reviewsOnLoadMore,
|
||||
'data-show-load-more': showLoadMore,
|
||||
'data-show-orderby': showOrderby,
|
||||
};
|
||||
let className = 'wc-block-all-reviews';
|
||||
|
||||
if ( productId ) {
|
||||
data[ 'data-product-id' ] = productId;
|
||||
className = 'wc-block-reviews-by-product';
|
||||
}
|
||||
|
||||
if ( Array.isArray( categoryIds ) ) {
|
||||
data[ 'data-category-ids' ] = categoryIds.join( ',' );
|
||||
className = 'wc-block-reviews-by-category';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ getBlockClassName( className, attributes ) }
|
||||
{ ...data }
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import classNames from 'classnames';
|
||||
import { ENABLE_REVIEW_RATING } from '@woocommerce/block-settings';
|
||||
|
||||
export const getSortArgs = ( sortValue ) => {
|
||||
if ( ENABLE_REVIEW_RATING ) {
|
||||
if ( sortValue === 'lowest-rating' ) {
|
||||
return {
|
||||
order: 'asc',
|
||||
orderby: 'rating',
|
||||
};
|
||||
}
|
||||
if ( sortValue === 'highest-rating' ) {
|
||||
return {
|
||||
order: 'desc',
|
||||
orderby: 'rating',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
order: 'desc',
|
||||
orderby: 'date_gmt',
|
||||
};
|
||||
};
|
||||
|
||||
export const getReviews = ( args ) => {
|
||||
return apiFetch( {
|
||||
path:
|
||||
'/wc/blocks/products/reviews?' +
|
||||
Object.entries( args )
|
||||
.map( ( arg ) => arg.join( '=' ) )
|
||||
.join( '&' ),
|
||||
parse: false,
|
||||
} ).then( ( response ) => {
|
||||
return response.json().then( ( reviews ) => {
|
||||
const totalReviews = parseInt(
|
||||
response.headers.get( 'x-wp-total' ),
|
||||
10
|
||||
);
|
||||
return { reviews, totalReviews };
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
export const getBlockClassName = ( blockClassName, attributes ) => {
|
||||
const {
|
||||
className,
|
||||
showReviewDate,
|
||||
showReviewerName,
|
||||
showReviewContent,
|
||||
showProductName,
|
||||
showReviewImage,
|
||||
showReviewRating,
|
||||
} = attributes;
|
||||
|
||||
return classNames( blockClassName, className, {
|
||||
'has-image': showReviewImage,
|
||||
'has-name': showReviewerName,
|
||||
'has-date': showReviewDate,
|
||||
'has-rating': showReviewRating,
|
||||
'has-content': showReviewContent,
|
||||
'has-product-name': showProductName,
|
||||
} );
|
||||
};
|
||||
Reference in New Issue
Block a user