diff --git a/assets/js/api-search/index.js b/assets/js/api-search/index.js index 87d610e013..5647c55d1f 100644 --- a/assets/js/api-search/index.js +++ b/assets/js/api-search/index.js @@ -12,6 +12,7 @@ import { WPElement, } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; +import { applyFilters } from '@wordpress/hooks'; /** * Internal dependencies. @@ -316,7 +317,16 @@ export const ApiSearchProvider = ({ return; } - setResults(response); + /** + * Filter the search results response. + * + * @filter ep.InstantResults.response + * @since 5.3.0 + * + * @param {object} response The search results response. + * @returns {object} Filtered search results response. + */ + setResults(applyFilters('ep.InstantResults.response', response)); } catch (e) { const errorMessage = sprintf( /* translators: Error message */ diff --git a/assets/js/instant-results/components/common/result.js b/assets/js/instant-results/components/common/result.js index 9e814fa35c..8e8f1093ef 100644 --- a/assets/js/instant-results/components/common/result.js +++ b/assets/js/instant-results/components/common/result.js @@ -17,6 +17,8 @@ import Image from './image'; * @param {number} props.averageRating Average rating. * @param {string} props.date Localized date. * @param {string} props.excerpt Highlighted excerpt. + * @param {object} props.hit Elasticsearch hit. + * @param {number} props.id Post ID. * @param {string} props.priceHtml Product price HTML. * @param {object} props.thumbnail Thumbnail image attributes. * @param {string} props.title Highlighted title. @@ -24,7 +26,9 @@ import Image from './image'; * @param {string} props.url URL. * @returns {WPElement} Component element. */ -const Result = ({ averageRating = 0, date, excerpt, priceHtml, thumbnail, title, type, url }) => { +const Result = (props) => { + const { averageRating = 0, date, excerpt, priceHtml, thumbnail, title, type, url } = props; + return (
{ const defaultIsOpen = index < 2; + /** + * Filter the filter label. + * + * @filter ep.InstantResults.filter.label + * @since 5.4.0 + * + * @param {string} label Filter label. + * @param {string} name Filter name. + * @param {string} type Filter type. + * @param {string[]} postTypes Filter post types. + * @returns {string} Filtered filter label. + */ + const filteredLabel = applyFilters( + 'ep.InstantResults.filter.label', + label, + name, + type, + postTypes, + ); + switch (type) { case 'post_type': - return ; + return ; case 'price_range': - return ; + return ; case 'taxonomy': return ( diff --git a/assets/js/instant-results/components/facets/post-type-facet.js b/assets/js/instant-results/components/facets/post-type-facet.js index 5a92024ae9..8cfddfc6b6 100644 --- a/assets/js/instant-results/components/facets/post-type-facet.js +++ b/assets/js/instant-results/components/facets/post-type-facet.js @@ -3,6 +3,7 @@ */ import { useCallback, useMemo, WPElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { applyFilters } from '@wordpress/hooks'; /** * Internal dependencies. @@ -65,7 +66,19 @@ export default ({ defaultIsOpen, label }) => { /** * Reduce buckets to options. */ - const options = useMemo(() => buckets.reduce(reduceOptions, []), [buckets, reduceOptions]); + const options = useMemo(() => { + const reducedOptions = buckets.reduce(reduceOptions, []); + /** + * Filter the post type filter options. + * + * @filter ep.InstantResults.filter.postType.options + * @since 5.4.0 + * + * @param {object[]} options Post type filter options. + * @returns {object[]} Filtered post type filter options. + */ + return applyFilters('ep.InstantResults.filter.postType.options', reducedOptions); + }, [buckets, reduceOptions]); /** * Handle checkbox change event. diff --git a/assets/js/instant-results/components/layout.js b/assets/js/instant-results/components/layout.js index 1a9f1d4bde..ee6b69d5bf 100644 --- a/assets/js/instant-results/components/layout.js +++ b/assets/js/instant-results/components/layout.js @@ -9,13 +9,9 @@ import { useState, WPElement } from '@wordpress/element'; import { useApiSearch } from '../../api-search'; import { facets } from '../config'; import Facet from './facets/facet'; -import SearchTermFacet from './facets/search-term-facet'; +import Header from './layout/header'; import Results from './layout/results'; import Sidebar from './layout/sidebar'; -import Toolbar from './layout/toolbar'; -import ActiveConstraints from './tools/active-constraints'; -import ClearConstraints from './tools/clear-constraints'; -import SidebarToggle from './tools/sidebar-toggle'; import Sort from './tools/sort'; /** @@ -39,15 +35,7 @@ export default () => { return (
-
- - - - - - - -
+
diff --git a/assets/js/instant-results/components/layout/header.js b/assets/js/instant-results/components/layout/header.js new file mode 100644 index 0000000000..553853e84f --- /dev/null +++ b/assets/js/instant-results/components/layout/header.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies. + */ +import { Component, FunctionComponent, WPElement } from '@wordpress/element'; +import { applyFilters } from '@wordpress/hooks'; + +/** + * Internal dependencies. + */ +import SearchTermFacet from '../facets/search-term-facet'; +import ActiveConstraints from '../tools/active-constraints'; +import ClearConstraints from '../tools/clear-constraints'; +import SidebarToggle from '../tools/sidebar-toggle'; +import Toolbar from './toolbar'; + +/** + * Search page header: the search input and toolbar. + * + * @param {object} props Component props. + * @param {boolean} props.isSidebarOpen Whether the sidebar is open. + * @param {Function} props.onClickSidebarToggle Sidebar toggle click handler. + * @returns {WPElement} Component element. + */ +const Header = ({ isSidebarOpen, onClickSidebarToggle }) => { + return ( +
+ + + + + + + +
+ ); +}; + +/** + * Filter the Header component. + * + * @filter ep.InstantResults.Header + * @since 5.4.0 + * + * @param {Component|FunctionComponent} Header Header component. + * @returns {Component|FunctionComponent} Header component. + */ +export default applyFilters('ep.InstantResults.Header', Header); diff --git a/assets/js/instant-results/components/layout/results.js b/assets/js/instant-results/components/layout/results.js index 4e1ff237bd..41ed3e022d 100644 --- a/assets/js/instant-results/components/layout/results.js +++ b/assets/js/instant-results/components/layout/results.js @@ -9,7 +9,7 @@ import { _n, sprintf, __ } from '@wordpress/i18n'; */ import { useApiSearch } from '../../../api-search'; import Pagination from '../results/pagination'; -import Result from '../results/result'; +import ResultsList from '../results/results-list'; import Sort from '../tools/sort'; import DidYouMean from '../results/did-you-mean'; @@ -20,6 +20,7 @@ import DidYouMean from '../results/did-you-mean'; */ export default () => { const { + aggregations, args: { offset, per_page, highlight }, nextPage, previousPage, @@ -106,9 +107,13 @@ export default () => { suggestedTerms={suggestedTerms} totalResults={totalResults} /> - {searchResults.map((hit) => ( - - ))} + { // Get other terms by excluding the first term from suggestedTerms const otherTerms = suggestedTerms.slice(1); - return ( + const didYouMean = ( <> {showSuggestions && suggestedTerms && suggestedTerms?.[0]?.text && (
@@ -67,4 +68,16 @@ export default (props) => { )} ); + + /** + * Filter the did you mean component. + * + * @filter ep.InstantResults.component.didYouMean + * @since 5.4.0 + * + * @param {WPElement} didYouMean Did you mean component. + * @param {object} props Props. + * @returns {WPElement} Did you mean component. + */ + return applyFilters('ep.InstantResults.component.didYouMean', didYouMean, props); }; diff --git a/assets/js/instant-results/components/results/pagination.js b/assets/js/instant-results/components/results/pagination.js index 4c10acb7d1..09eb8dd306 100644 --- a/assets/js/instant-results/components/results/pagination.js +++ b/assets/js/instant-results/components/results/pagination.js @@ -22,7 +22,9 @@ import { isNumberedPagination } from '../../config'; * @param {number} props.total Total number of items. * @returns {WPElement} Element. */ -const Pagination = ({ offset, onNext, onPage = {}, onPrevious, perPage, total }) => { +const Pagination = (props) => { + const { offset, onNext, onPage = {}, onPrevious, perPage, total } = props; + if (perPage <= 0) { return null; } @@ -186,11 +188,23 @@ const Pagination = ({ offset, onNext, onPage = {}, onPrevious, perPage, total }) ); }; - return ( + const pagination = ( ); + + /** + * Filter the pagination component. + * + * @filter ep.InstantResults.component.pagination + * @since 5.4.0 + * + * @param {WPElement} pagination Pagination component. + * @param {object} props Props. + * @returns {WPElement} Pagination component. + */ + return applyFilters('ep.InstantResults.component.pagination', pagination, props); }; /** diff --git a/assets/js/instant-results/components/results/results-list.js b/assets/js/instant-results/components/results/results-list.js new file mode 100644 index 0000000000..5a0d50b43a --- /dev/null +++ b/assets/js/instant-results/components/results/results-list.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies. + */ +import { Component, FunctionComponent, WPElement } from '@wordpress/element'; +import { applyFilters } from '@wordpress/hooks'; + +/** + * Internal dependencies. + */ +import Result from './result'; + +/** + * List of search results. + * + * @param {object} props Component props. + * @param {object} props.aggregations Elasticsearch aggregations for the current query. + * @param {string} props.highlightTag Selected highlight tag. + * @param {object[]} props.searchResults Elasticsearch hits. + * @param {string} props.searchTerm Search term from input search. + * @param {number} props.totalResults Total number of results across all pages. + * @returns {WPElement} Component element. + */ +const ResultsList = (props) => { + const { highlightTag, searchResults, searchTerm } = props; + + return ( + <> + {searchResults.map((hit) => ( + + ))} + + ); +}; + +/** + * Filter the ResultsList component. + * + * @filter ep.InstantResults.ResultsList + * @since 5.4.0 + * + * @param {Component|FunctionComponent} ResultsList ResultsList component. + * @returns {Component|FunctionComponent} ResultsList component. + */ +export default applyFilters('ep.InstantResults.ResultsList', ResultsList); diff --git a/assets/js/instant-results/index.js b/assets/js/instant-results/index.js index 24884764ac..a660f3a236 100644 --- a/assets/js/instant-results/index.js +++ b/assets/js/instant-results/index.js @@ -2,13 +2,36 @@ * WordPress dependencies. */ import { createRoot, render } from '@wordpress/element'; +import { doAction } from '@wordpress/hooks'; /** * Internal dependencies. */ -import { ApiSearchProvider } from '../api-search'; +import { ApiSearchProvider, useApiSearch } from '../api-search'; import { apiEndpoint, apiHost, argsSchema, paramPrefix, requestIdBase } from './config'; import Modal from './apps/modal'; +import SearchTermFacet from './components/facets/search-term-facet'; +import Toolbar from './components/layout/toolbar'; +import ActiveConstraints from './components/tools/active-constraints'; +import ClearConstraints from './components/tools/clear-constraints'; +import SidebarToggle from './components/tools/sidebar-toggle'; + +/** + * Expose the components and the useApiSearch hook. + * + * @action ep.InstantResults.ready + * @since 5.4.0 + */ +doAction('ep.InstantResults.ready', { + components: { + ActiveConstraints, + ClearConstraints, + SearchTermFacet, + SidebarToggle, + Toolbar, + }, + useApiSearch, +}); /** * Initialize Instant Results.