From 606d193792d0f8ac24fdd018dbe3fd0a265ff790 Mon Sep 17 00:00:00 2001 From: Razvan Date: Thu, 29 Feb 2024 18:08:41 +0200 Subject: [PATCH 01/12] feat(search): show facets count --- .../Blocks/Listing/withQuerystringResults.jsx | 162 +++++++ .../manage/Blocks/Search/SearchBlockEdit.jsx | 100 ++++ .../manage/Blocks/Search/SearchBlockView.jsx | 112 +++++ .../Search/components/CheckboxFacet.jsx | 71 +++ .../Blocks/Search/components/Facets.jsx | 106 ++++ .../Blocks/Search/components/SelectFacet.jsx | 67 +++ .../manage/Blocks/Search/hocs/index.js | 3 + .../Blocks/Search/hocs/withFacetsCount.jsx | 26 + .../Blocks/Search/hocs/withQueryString.jsx | 32 ++ .../manage/Blocks/Search/hocs/withSearch.jsx | 454 ++++++++++++++++++ .../Blocks/Search/layout/LeftColumnFacets.jsx | 156 ++++++ .../Search/layout/RightColumnFacets.jsx | 157 ++++++ .../Blocks/Search/layout/TopSideFacets.jsx | 157 ++++++ .../querystringsearch/querystringsearch.js | 131 +++++ 14 files changed, 1734 insertions(+) create mode 100644 src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/SearchBlockEdit.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/hocs/index.js create mode 100644 src/customizations/volto/components/manage/Blocks/Search/hocs/withFacetsCount.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/hocs/withQueryString.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/hocs/withSearch.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/layout/LeftColumnFacets.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/layout/RightColumnFacets.jsx create mode 100644 src/customizations/volto/components/manage/Blocks/Search/layout/TopSideFacets.jsx create mode 100644 src/customizations/volto/reducers/querystringsearch/querystringsearch.js diff --git a/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx b/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx new file mode 100644 index 00000000..25401855 --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx @@ -0,0 +1,162 @@ +import React, { useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import useDeepCompareEffect from 'use-deep-compare-effect'; + +import { getContent, getQueryStringResults } from '@plone/volto/actions'; +import { usePagination, getBaseUrl } from '@plone/volto/helpers'; + +import config from '@plone/volto/registry'; + +function getDisplayName(WrappedComponent) { + return WrappedComponent.displayName || WrappedComponent.name || 'Component'; +} + +export default function withQuerystringResults(WrappedComponent) { + function WithQuerystringResults(props) { + const { + data = {}, + id = data.block, + properties: content, + path, + variation, + } = props; + const { settings } = config; + const querystring = data.querystring || data; // For backwards compat with data saved before Blocks schema. Note, this is also how the Search block passes data to ListingBody + const subrequestID = content?.UID ? `${content?.UID}-${id}` : id; + const { b_size = settings.defaultPageSize } = querystring; // batchsize + + // save the path so it won't trigger dispatch on eager router location change + const [initialPath] = React.useState(getBaseUrl(path)); + + const copyFields = [ + 'limit', + 'query', + 'sort_on', + 'sort_order', + 'depth', + 'facets', + ]; + const { currentPage, setCurrentPage } = usePagination(id, 1); + const adaptedQuery = Object.assign( + variation?.fullobjects ? { fullobjects: 1 } : { metadata_fields: '_all' }, + { + b_size: b_size, + }, + ...copyFields.map((name) => + Object.keys(querystring).includes(name) + ? { [name]: querystring[name] } + : {}, + ), + ); + const adaptedQueryRef = useRef(adaptedQuery); + const currentPageRef = useRef(currentPage); + + const querystringResults = useSelector( + (state) => state.querystringsearch.subrequests, + ); + const dispatch = useDispatch(); + + const folderItems = content?.is_folderish ? content.items : []; + const hasQuery = querystring?.query?.length > 0; + const hasLoaded = hasQuery + ? querystringResults?.[subrequestID]?.loaded + : true; + + const listingItems = hasQuery + ? querystringResults?.[subrequestID]?.items || [] + : folderItems; + + const showAsFolderListing = !hasQuery && content?.items_total > b_size; + const showAsQueryListing = + hasQuery && querystringResults?.[subrequestID]?.total > b_size; + + const totalPages = showAsFolderListing + ? Math.ceil(content.items_total / b_size) + : showAsQueryListing + ? Math.ceil(querystringResults[subrequestID].total / b_size) + : 0; + + const prevBatch = showAsFolderListing + ? content.batching?.prev + : showAsQueryListing + ? querystringResults[subrequestID].batching?.prev + : null; + const nextBatch = showAsFolderListing + ? content.batching?.next + : showAsQueryListing + ? querystringResults[subrequestID].batching?.next + : null; + + const isImageGallery = + (!data.variation && data.template === 'imageGallery') || + data.variation === 'imageGallery'; + + useDeepCompareEffect(() => { + if (hasQuery) { + dispatch( + getQueryStringResults( + initialPath, + adaptedQuery, + subrequestID, + currentPage, + ), + ); + } else if (isImageGallery && !hasQuery) { + // when used as image gallery, it doesn't need a query to list children + dispatch( + getQueryStringResults( + initialPath, + { + ...adaptedQuery, + b_size: 10000000000, + query: [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.relativePath', + v: '', + }, + ], + }, + subrequestID, + ), + ); + } else { + dispatch(getContent(initialPath, null, null, currentPage)); + } + adaptedQueryRef.current = adaptedQuery; + currentPageRef.current = currentPage; + }, [ + subrequestID, + isImageGallery, + adaptedQuery, + hasQuery, + initialPath, + dispatch, + currentPage, + ]); + + return ( + setCurrentPage(activePage)} + total={querystringResults?.[subrequestID]?.total} + batch_size={b_size} + currentPage={currentPage} + totalPages={totalPages} + prevBatch={prevBatch} + nextBatch={nextBatch} + listingItems={listingItems} + facetsCount={querystringResults?.[subrequestID]?.facets_count} + hasLoaded={hasLoaded} + isFolderContentsListing={showAsFolderListing} + /> + ); + } + + WithQuerystringResults.displayName = `WithQuerystringResults(${getDisplayName( + WrappedComponent, + )})`; + + return hoistNonReactStatics(WithQuerystringResults, WrappedComponent); +} diff --git a/src/customizations/volto/components/manage/Blocks/Search/SearchBlockEdit.jsx b/src/customizations/volto/components/manage/Blocks/Search/SearchBlockEdit.jsx new file mode 100644 index 00000000..adf4d1ce --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Search/SearchBlockEdit.jsx @@ -0,0 +1,100 @@ +import React, { useEffect } from 'react'; +import { defineMessages } from 'react-intl'; +import { compose } from 'redux'; + +import { SidebarPortal, BlockDataForm } from '@plone/volto/components'; +import { addExtensionFieldToSchema } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer'; +import { getBaseUrl } from '@plone/volto/helpers'; +import config from '@plone/volto/registry'; + +import { SearchBlockViewComponent } from './SearchBlockView'; +import Schema from '@plone/volto/components/manage/Blocks/Search/schema'; +import { withSearch, withQueryString, withFacetsCount } from './hocs'; +import { cloneDeep } from 'lodash'; + +const messages = defineMessages({ + template: { + id: 'Results template', + defaultMessage: 'Results template', + }, +}); + +const SearchBlockEdit = (props) => { + const { + block, + onChangeBlock, + data, + selected, + intl, + navRoot, + contentType, + onTriggerSearch, + querystring = {}, + } = props; + const { sortable_indexes = {} } = querystring; + + let schema = Schema({ data, intl }); + + schema = addExtensionFieldToSchema({ + schema, + name: 'listingBodyTemplate', + items: config.blocks.blocksConfig.listing.variations, + intl, + title: { id: intl.formatMessage(messages.template) }, + }); + const listingVariations = config.blocks.blocksConfig?.listing?.variations; + let activeItem = listingVariations.find( + (item) => item.id === data.listingBodyTemplate, + ); + const listingSchemaEnhancer = activeItem?.schemaEnhancer; + if (listingSchemaEnhancer) + schema = listingSchemaEnhancer({ + schema: cloneDeep(schema), + data, + intl, + }); + schema.properties.sortOnOptions.items = { + choices: Object.keys(sortable_indexes).map((k) => [ + k, + sortable_indexes[k].title, + ]), + }; + + const { query = {} } = data || {}; + // We don't need deep compare here, as this is just json serializable data. + const deepQuery = JSON.stringify(query); + useEffect(() => { + onTriggerSearch(); + }, [deepQuery, onTriggerSearch]); + + return ( + <> + + + { + onChangeBlock(block, { + ...data, + [id]: value, + }); + }} + onChangeBlock={onChangeBlock} + formData={data} + navRoot={navRoot} + contentType={contentType} + /> + + + ); +}; + +export default compose( + withQueryString, + withFacetsCount, + withSearch(), +)(SearchBlockEdit); diff --git a/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx b/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx new file mode 100644 index 00000000..ac80db4c --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx @@ -0,0 +1,112 @@ +import React from 'react'; + +import ListingBody from '@plone/volto/components/manage/Blocks/Listing/ListingBody'; +import { withBlockExtensions } from '@plone/volto/helpers'; + +import config from '@plone/volto/registry'; + +import { withSearch, withQueryString, withFacetsCount } from './hocs'; +import { compose } from 'redux'; +import { useSelector } from 'react-redux'; +import { isEqual, isFunction } from 'lodash'; + +const getListingBodyVariation = (data) => { + const { variations } = config.blocks.blocksConfig.listing; + + let variation = data.listingBodyTemplate + ? variations.find(({ id }) => id === data.listingBodyTemplate) + : variations.find(({ isDefault }) => isDefault); + + if (!variation) variation = variations[0]; + + return variation; +}; + +const isfunc = (obj) => isFunction(obj) || typeof obj === 'function'; + +const _filtered = (obj) => + Object.assign( + {}, + ...Object.keys(obj).map((k) => { + const reject = k !== 'properties' && !isfunc(obj[k]); + return reject ? { [k]: obj[k] } : {}; + }), + ); + +const blockPropsAreChanged = (prevProps, nextProps) => { + const prev = _filtered(prevProps); + const next = _filtered(nextProps); + + return isEqual(prev, next); +}; + +const applyDefaults = (data, root) => { + const defaultQuery = [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.absolutePath', + v: root || '/', + }, + ]; + return { + ...data, + sort_on: data?.sort_on || 'effective', + sort_order: data?.sort_order || 'descending', + query: data?.query?.length ? data.query : defaultQuery, + // facets: (data?.facets || []).map((facet) => facet.field), + }; +}; + +const SearchBlockView = (props) => { + const { data, searchData, mode = 'view', variation } = props; + + const Layout = variation.view; + + const dataListingBodyVariation = getListingBodyVariation(data).id; + const [selectedView, setSelectedView] = React.useState( + dataListingBodyVariation, + ); + + // in the block edit you can change the used listing block variation, + // but it's cached here in the state. So we reset it. + React.useEffect(() => { + if (mode !== 'view') { + setSelectedView(dataListingBodyVariation); + } + }, [dataListingBodyVariation, mode]); + + const root = useSelector((state) => state.breadcrumbs.root); + const listingBodyData = applyDefaults(searchData, root); + + const { variations } = config.blocks.blocksConfig.listing; + const listingBodyVariation = variations.find(({ id }) => id === selectedView); + + return ( +
+ + + +
+ ); +}; + +export const SearchBlockViewComponent = compose( + withBlockExtensions, + (Component) => React.memo(Component, blockPropsAreChanged), +)(SearchBlockView); + +export default compose( + withQueryString, + withFacetsCount, + withSearch(), +)(SearchBlockViewComponent); diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx new file mode 100644 index 00000000..96cc48c5 --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { Checkbox, Header } from 'semantic-ui-react'; +import { + selectFacetSchemaEnhancer, + selectFacetStateToValue, + selectFacetValueToQuery, +} from '@plone/volto/components/manage/Blocks/Search/components/base'; + +/** + * A facet that uses radio/checkboxes to provide an explicit list of values for + * filtering + */ +const CheckboxFacet = (props) => { + const { + facet, + facetCount, + choices, + isMulti, + onChange, + value, + isEditMode, + } = props; + const facetValue = value; + + return ( +
+
{facet.title ?? facet?.field?.label}
+
+ {choices.map(({ label, value }, i) => { + const count = facetCount?.data?.[value] || 0; + + return ( +
+ f.value === value) + : facetValue && facetValue.value === value + } + onChange={(e, { checked }) => + onChange( + facet.field.value, + isMulti + ? [ + ...facetValue + .filter((f) => f.value !== value) + .map((f) => f.value), + ...(checked ? [value] : []), + ] + : checked + ? value + : null, + ) + } + /> +
+ ); + })} +
+
+ ); +}; + +CheckboxFacet.schemaEnhancer = selectFacetSchemaEnhancer; +CheckboxFacet.stateToValue = selectFacetStateToValue; +CheckboxFacet.valueToQuery = selectFacetValueToQuery; + +export default CheckboxFacet; diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx new file mode 100644 index 00000000..c855515b --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions'; +import config from '@plone/volto/registry'; +import { + hasNonValueOperation, + hasDateOperation, +} from '@plone/volto/components/manage/Blocks/Search/utils'; + +const defaultShowFacet = (index) => { + const { values } = index; + return index + ? hasNonValueOperation(index.operations || []) || + hasDateOperation(index.operations || []) + ? true + : values && Object.keys(values).length > 0 + : values && Object.keys(values).length > 0; +}; + +const Facets = (props) => { + const { + querystring, + data = {}, + facets, + setFacets, + facetWrapper, + isEditMode, + } = props; + const { search } = config.blocks.blocksConfig; + + const FacetWrapper = facetWrapper; + const query_to_values = Object.assign( + {}, + ...(data?.query?.query?.map(({ i, v }) => ({ [i]: v })) || []), + ); + + return ( + <> + {data?.facets + ?.filter((facetSettings) => !facetSettings.hidden) + .map((facetSettings) => { + const field = facetSettings?.field?.value; + const index = querystring.indexes[field] || {}; + const { values = {} } = index; + const facetCount = props.facetsCount?.[facetSettings.field.value]; + + let choices = Object.keys(values) + .map((name) => ({ + value: name, + label: values[name].title, + })) + // filter the available values based on the allowed values in the + // base query + .filter(({ value }) => + query_to_values[field] + ? query_to_values[field].includes(value) + : true, + ); + + choices = choices.sort((a, b) => + a.label.localeCompare(b.label, 'en', { sensitivity: 'base' }), + ); + + const isMulti = facetSettings.multiple; + const selectedValue = facets[facetSettings?.field?.value]; + + // TODO :handle changing the type of facet (multi/nonmulti) + + const { + view: FacetWidget, + stateToValue, + showFacet = defaultShowFacet, + } = resolveExtension( + 'type', + search.extensions.facetWidgets.types, + facetSettings, + ); + + let value = stateToValue({ facetSettings, index, selectedValue }); + + const { + rewriteOptions = (name, options) => options, + } = search.extensions.facetWidgets; + + return FacetWrapper && (isEditMode || showFacet(index)) ? ( + + { + !isEditMode && setFacets({ ...facets, [id]: value }); + }} + /> + + ) : ( + '' + ); + })} + + ); +}; + +export default Facets; diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx new file mode 100644 index 00000000..a6a9cafe --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; +import { + Option, + DropdownIndicator, + MultiValueContainer, +} from '@plone/volto/components/manage/Widgets/SelectStyling'; +import { + selectTheme, + customSelectStyles, +} from '@plone/volto/components/manage/Blocks/Search/components/SelectStyling'; +import { + selectFacetSchemaEnhancer, + selectFacetStateToValue, + selectFacetValueToQuery, +} from '@plone/volto/components/manage/Blocks/Search/components/base'; + +const SelectFacet = (props) => { + const { + facet, + facetCount, + choices, + reactSelect, + isMulti, + onChange, + value, + isEditMode, + } = props; + const Select = reactSelect.default; + const v = Array.isArray(value) && value.length === 0 ? null : value; + + return ( + { - if (data) { - onChange( - facet.field.value, - isMulti ? data.map(({ value }) => value) : data.value, - ); - } else { - // data has been removed - onChange(facet.field.value, isMulti ? [] : ''); - } - }} - getOptionLabel={({ label, value }) => { - return `${label} (${facetCount?.data?.[value] || 0})`; - }} - isMulti={facet.multiple} - isClearable - value={v} - /> - ); + if (isFacetCountEnabled) + return ( + { + if (data) { + onChange( + facet.field.value, + isMulti ? data.map(({ value }) => value) : data.value, + ); + } else { + // data has been removed + onChange(facet.field.value, isMulti ? [] : ''); + } + }} + isMulti={facet.multiple} + isClearable + value={v} + /> + ); }; SelectFacet.schemaEnhancer = selectFacetSchemaEnhancer; From 2e14c6a91dc48b86814e8cb3f75881cbd6afe242 Mon Sep 17 00:00:00 2001 From: Dobricean Ioan Dorian Date: Mon, 11 Mar 2024 09:20:13 +0200 Subject: [PATCH 09/12] fix tests --- .../Blocks/Listing/withQuerystringResults.jsx | 12 +- .../manage/Blocks/Search/SearchBlockView.jsx | 195 +++++++++--------- .../Search/components/CheckboxFacet.jsx | 10 +- .../Blocks/Search/components/Facets.jsx | 14 +- .../manage/Blocks/Search/hocs/withSearch.jsx | 10 +- 5 files changed, 129 insertions(+), 112 deletions(-) diff --git a/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx b/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx index 6e3010af..2d394c72 100644 --- a/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx +++ b/src/customizations/volto/components/manage/Blocks/Listing/withQuerystringResults.jsx @@ -74,19 +74,19 @@ export default function withQuerystringResults(WrappedComponent) { const totalPages = showAsFolderListing ? Math.ceil(content.items_total / b_size) : showAsQueryListing - ? Math.ceil(querystringResults[subrequestID].total / b_size) - : 0; + ? Math.ceil(querystringResults[subrequestID].total / b_size) + : 0; const prevBatch = showAsFolderListing ? content.batching?.prev : showAsQueryListing - ? querystringResults[subrequestID].batching?.prev - : null; + ? querystringResults[subrequestID].batching?.prev + : null; const nextBatch = showAsFolderListing ? content.batching?.next : showAsQueryListing - ? querystringResults[subrequestID].batching?.next - : null; + ? querystringResults[subrequestID].batching?.next + : null; const isImageGallery = (!data.variation && data.template === 'imageGallery') || diff --git a/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx b/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx index b6c7c923..0c2c4bb9 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/SearchBlockView.jsx @@ -1,100 +1,111 @@ import React from 'react'; -import { Checkbox, Header } from 'semantic-ui-react'; -import { - selectFacetSchemaEnhancer, - selectFacetStateToValue, - selectFacetValueToQuery, -} from './base'; - -/** - * A facet that uses radio/checkboxes to provide an explicit list of values for - * filtering - */ -const CheckboxFacet = (props) => { - const { - facet, - facetCount, - choices, - isMulti, - onChange, - value, - isEditMode, - isFacetCountEnabled, - } = props; - const facetValue = value; + +import ListingBody from '@plone/volto/components/manage/Blocks/Listing/ListingBody'; +import { withBlockExtensions } from '@plone/volto/helpers'; + +import config from '@plone/volto/registry'; + +import { withSearch, withQueryString, withFacetsCount } from './hocs'; +import { compose } from 'redux'; +import { useSelector } from 'react-redux'; +import { isEqual, isFunction } from 'lodash'; +import cx from 'classnames'; + +const getListingBodyVariation = (data) => { + const { variations } = config.blocks.blocksConfig.listing; + + let variation = data.listingBodyTemplate + ? variations.find(({ id }) => id === data.listingBodyTemplate) + : variations.find(({ isDefault }) => isDefault); + + if (!variation) variation = variations[0]; + + return variation; +}; + +const isfunc = (obj) => isFunction(obj) || typeof obj === 'function'; + +const _filtered = (obj) => + Object.assign( + {}, + ...Object.keys(obj).map((k) => { + const reject = k !== 'properties' && !isfunc(obj[k]); + return reject ? { [k]: obj[k] } : {}; + }), + ); + +const blockPropsAreChanged = (prevProps, nextProps) => { + const prev = _filtered(prevProps); + const next = _filtered(nextProps); + + return isEqual(prev, next); +}; + +const applyDefaults = (data, root) => { + const defaultQuery = [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.absolutePath', + v: root || '/', + }, + ]; + return { + ...data, + sort_on: data?.sort_on || 'effective', + sort_order: data?.sort_order || 'descending', + query: data?.query?.length ? data.query : defaultQuery, + }; +}; + +const SearchBlockView = (props) => { + const { id, data, searchData, mode = 'view', variation, className } = props; + + const Layout = variation.view; + + const dataListingBodyVariation = getListingBodyVariation(data).id; + const [selectedView, setSelectedView] = React.useState( + dataListingBodyVariation, + ); + + // in the block edit you can change the used listing block variation, + // but it's cached here in the state. So we reset it. + React.useEffect(() => { + if (mode !== 'view') { + setSelectedView(dataListingBodyVariation); + } + }, [dataListingBodyVariation, mode]); + + const root = useSelector((state) => state.breadcrumbs.root); + const listingBodyData = applyDefaults(searchData, root); + + const { variations } = config.blocks.blocksConfig.listing; + const listingBodyVariation = variations.find(({ id }) => id === selectedView); return ( -
-
{facet.title ?? facet?.field?.label}
-
- {choices.map(({ label, value }, i) => { - const count = facetCount?.data?.[value] || 0; - - return ( -
- {isFacetCountEnabled === true ? ( - f.value === value) - : facetValue && facetValue.value === value - } - onChange={(e, { checked }) => - onChange( - facet.field.value, - isMulti - ? [ - ...facetValue - .filter((f) => f.value !== value) - .map((f) => f.value), - ...(checked ? [value] : []), - ] - : checked - ? value - : null, - ) - } - /> - ) : ( - f.value === value) - : facetValue && facetValue.value === value - } - onChange={(e, { checked }) => - onChange( - facet.field.value, - isMulti - ? [ - ...facetValue - .filter((f) => f.value !== value) - .map((f) => f.value), - ...(checked ? [value] : []), - ] - : checked - ? value - : null, - ) - } - /> - )} -
- ); - })} -
+
+ + +
); }; -CheckboxFacet.schemaEnhancer = selectFacetSchemaEnhancer; -CheckboxFacet.stateToValue = selectFacetStateToValue; -CheckboxFacet.valueToQuery = selectFacetValueToQuery; +export const SearchBlockViewComponent = compose( + withBlockExtensions, + (Component) => React.memo(Component, blockPropsAreChanged), +)(SearchBlockView); -export default CheckboxFacet; +export default withSearch()( + withQueryString(withFacetsCount(SearchBlockViewComponent)), +); diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx index b6c7c923..97e1a6f6 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx @@ -4,7 +4,7 @@ import { selectFacetSchemaEnhancer, selectFacetStateToValue, selectFacetValueToQuery, -} from './base'; +} from '@plone/volto/src/components/manage/Blocks/Search/components/base'; /** * A facet that uses radio/checkboxes to provide an explicit list of values for @@ -53,8 +53,8 @@ const CheckboxFacet = (props) => { ...(checked ? [value] : []), ] : checked - ? value - : null, + ? value + : null, ) } /> @@ -79,8 +79,8 @@ const CheckboxFacet = (props) => { ...(checked ? [value] : []), ] : checked - ? value - : null, + ? value + : null, ) } /> diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx index 20e50eed..f04ca5a1 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx @@ -2,7 +2,10 @@ import React, { useState, useMemo } from 'react'; import { Button, Grid } from 'semantic-ui-react'; import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions'; import config from '@plone/volto/registry'; -import { hasNonValueOperation, hasDateOperation } from '../utils'; +import { + hasNonValueOperation, + hasDateOperation, +} from '@plone/volto/src/components/manage/Blocks/Search/utils'; import { defineMessages, useIntl } from 'react-intl'; const messages = defineMessages({ @@ -106,8 +109,9 @@ const Facets = (props) => { let value = stateToValue({ facetSettings, index, selectedValue }); - const { rewriteOptions = (name, options) => options } = - search.extensions.facetWidgets; + const { + rewriteOptions = (name, options) => options, + } = search.extensions.facetWidgets; return FacetWrapper && (isEditMode || showFacet(index)) ? ( { ? intl.formatMessage(messages.showFilters) : intl.formatMessage(messages.moreFilters) : advancedFilters === 2 - ? intl.formatMessage(messages.hideFilters) - : intl.formatMessage(messages.lessFilters)} + ? intl.formatMessage(messages.hideFilters) + : intl.formatMessage(messages.lessFilters)} )} diff --git a/src/customizations/volto/components/manage/Blocks/Search/hocs/withSearch.jsx b/src/customizations/volto/components/manage/Blocks/Search/hocs/withSearch.jsx index a62497ac..2f98fb8d 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/hocs/withSearch.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/hocs/withSearch.jsx @@ -37,8 +37,9 @@ function getInitialState( sortOnParam, sortOrderParam, ) { - const { types: facetWidgetTypes } = - config.blocks.blocksConfig.search.extensions.facetWidgets; + const { + types: facetWidgetTypes, + } = config.blocks.blocksConfig.search.extensions.facetWidgets; const facetSettings = data?.facets || []; return { @@ -100,8 +101,9 @@ function normalizeState({ sortOrder, facetSettings, // data.facets extracted from block data }) { - const { types: facetWidgetTypes } = - config.blocks.blocksConfig.search.extensions.facetWidgets; + const { + types: facetWidgetTypes, + } = config.blocks.blocksConfig.search.extensions.facetWidgets; // Here, we are removing the QueryString of the Listing ones, which is present in the Facet // because we already initialize the facet with those values. From 1d52333f2ae34f3edc0d3cf8cf6144aaac168041 Mon Sep 17 00:00:00 2001 From: Dobricean Ioan Dorian Date: Mon, 11 Mar 2024 09:28:36 +0200 Subject: [PATCH 10/12] fix tests --- .../manage/Blocks/Search/components/SelectFacet.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx index f43348d7..4942edb1 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/components/SelectFacet.jsx @@ -5,12 +5,15 @@ import { DropdownIndicator, MultiValueContainer, } from '@plone/volto/components/manage/Widgets/SelectStyling'; -import { selectTheme, customSelectStyles } from './SelectStyling'; +import { + selectTheme, + customSelectStyles, +} from '@plone/volto/components/manage/Blocks/Search/components/SelectStyling'; import { selectFacetSchemaEnhancer, selectFacetStateToValue, selectFacetValueToQuery, -} from './base'; +} from '@plone/volto/components/manage/Blocks/Search/components/base'; const SelectFacet = (props) => { const { From 86953d9c964154be7ce934fb87146a330305eb69 Mon Sep 17 00:00:00 2001 From: Dobricean Ioan Dorian Date: Mon, 11 Mar 2024 09:43:58 +0200 Subject: [PATCH 11/12] fix tests --- .../manage/Blocks/Search/components/Facets.jsx | 5 +---- .../components/manage/Blocks/Search/utils.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 src/customizations/volto/components/manage/Blocks/Search/utils.js diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx index f04ca5a1..f42ee7ac 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/components/Facets.jsx @@ -2,10 +2,7 @@ import React, { useState, useMemo } from 'react'; import { Button, Grid } from 'semantic-ui-react'; import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions'; import config from '@plone/volto/registry'; -import { - hasNonValueOperation, - hasDateOperation, -} from '@plone/volto/src/components/manage/Blocks/Search/utils'; +import { hasNonValueOperation, hasDateOperation } from '../utils'; import { defineMessages, useIntl } from 'react-intl'; const messages = defineMessages({ diff --git a/src/customizations/volto/components/manage/Blocks/Search/utils.js b/src/customizations/volto/components/manage/Blocks/Search/utils.js new file mode 100644 index 00000000..bab89c4c --- /dev/null +++ b/src/customizations/volto/components/manage/Blocks/Search/utils.js @@ -0,0 +1,16 @@ +export const NONVALUE_OPERATIONS = new Set([ + 'plone.app.querystring.operation.boolean.isFalse', + 'plone.app.querystring.operation.boolean.isTrue', +]); + +export const DATE_OPERATIONS = new Set([ + 'plone.app.querystring.operation.date.between', +]); + +export const hasNonValueOperation = (ops) => { + return ops.filter((x) => NONVALUE_OPERATIONS.has(x)).length > 0; +}; + +export const hasDateOperation = (ops) => { + return ops.filter((x) => DATE_OPERATIONS.has(x)).length > 0; +}; From cd7be54fcd01da0a0717d343ee477e3ff8299364 Mon Sep 17 00:00:00 2001 From: Dobricean Ioan Dorian Date: Mon, 11 Mar 2024 10:08:12 +0200 Subject: [PATCH 12/12] fix tests --- .../manage/Blocks/Search/components/CheckboxFacet.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx b/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx index 97e1a6f6..7162868f 100644 --- a/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx +++ b/src/customizations/volto/components/manage/Blocks/Search/components/CheckboxFacet.jsx @@ -4,7 +4,7 @@ import { selectFacetSchemaEnhancer, selectFacetStateToValue, selectFacetValueToQuery, -} from '@plone/volto/src/components/manage/Blocks/Search/components/base'; +} from '@plone/volto/components/manage/Blocks/Search/components/base'; /** * A facet that uses radio/checkboxes to provide an explicit list of values for