From 544d318e964a936ff4892ca8b67b55b14cfa406c Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Wed, 31 Aug 2022 22:32:40 +1000 Subject: [PATCH 1/5] Move Autosuggest script to directory. --- assets/js/{autosuggest.js => autosuggest/index.js} | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename assets/js/{autosuggest.js => autosuggest/index.js} (99%) diff --git a/assets/js/autosuggest.js b/assets/js/autosuggest/index.js similarity index 99% rename from assets/js/autosuggest.js rename to assets/js/autosuggest/index.js index 463fcef3b1..ccd9295354 100644 --- a/assets/js/autosuggest.js +++ b/assets/js/autosuggest/index.js @@ -9,7 +9,7 @@ import { replaceGlobally, debounce, domReady, -} from './utils/helpers'; +} from '../utils/helpers'; const { epas } = window; diff --git a/package.json b/package.json index 66119f6a3e..e970d0ed3a 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ }, "10up-toolkit": { "entry": { - "autosuggest-script.min": "./assets/js/autosuggest.js", + "autosuggest-script.min": "./assets/js/autosuggest/index.js", "comments-script.min": "./assets/js/comments.js", "dashboard-script.min": "./assets/js/dashboard.js", "facets-script.min": "./assets/js/facets.js", From e0adc071a934483af843df545c4f0ad997dc392f Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Wed, 31 Aug 2022 23:34:08 +1000 Subject: [PATCH 2/5] Migrate Autosuggest filters to proper WordPress hooks --- assets/js/autosuggest/back-compat.js | 40 +++++++++ assets/js/autosuggest/index.js | 123 ++++++++++++++++++--------- 2 files changed, 122 insertions(+), 41 deletions(-) create mode 100644 assets/js/autosuggest/back-compat.js diff --git a/assets/js/autosuggest/back-compat.js b/assets/js/autosuggest/back-compat.js new file mode 100644 index 0000000000..1f26c9ab5e --- /dev/null +++ b/assets/js/autosuggest/back-compat.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies. + */ +import { addFilter } from '@wordpress/hooks'; + +if (typeof window.epDataFilter !== 'undefined') { + addFilter('ep.Autosuggest.data', 'ep/epDatafilter', window.epDatafilter); +} + +if (typeof window.epAutosuggestItemHTMLFilter !== 'undefined') { + addFilter( + 'ep.Autosuggest.itemHTML', + 'ep/epAutosuggestItemHTMLFilter', + window.epAutosuggestItemHTMLFilter, + ); +} + +if (typeof window.epAutosuggestListItemsHTMLFilter !== 'undefined') { + addFilter( + 'ep.Autosuggest.listHTML', + 'ep/epAutosuggestListItemsHTMLFilter', + window.epAutosuggestListItemsHTMLFilter, + ); +} + +if (typeof window.epAutosuggestQueryFilter !== 'undefined') { + addFilter( + 'ep.Autosuggest.query', + 'ep/epAutosuggestQueryFilter', + window.epAutosuggestQueryFilter, + ); +} + +if (typeof window.epAutosuggestElementFilter !== 'undefined') { + addFilter( + 'ep.Autosuggest.element', + 'ep/epAutosuggestElementFilter', + window.epAutosuggestElementFilter, + ); +} diff --git a/assets/js/autosuggest/index.js b/assets/js/autosuggest/index.js index ccd9295354..b70728c1ea 100644 --- a/assets/js/autosuggest/index.js +++ b/assets/js/autosuggest/index.js @@ -1,8 +1,14 @@ -/* eslint-disable camelcase, no-underscore-dangle, no-use-before-define */ +/* eslint-disable camelcase, no-underscore-dangle, no-use-before-define, jsdoc/check-tag-names */ + +/** + * WordPress dependencies. + */ +import { applyFilters } from '@wordpress/hooks'; /** * Internal dependencies. */ +import './back-compat'; import { findAncestorByClass, escapeDoubleQuotes, @@ -195,13 +201,16 @@ async function esSearch(query, searchTerm) { const data = await response.json(); - // allow for filtered data before returning it to - // be output on the front end - if (typeof window.epDataFilter !== 'undefined') { - return window.epDataFilter(data, searchTerm); - } - - return data; + /** + * Filter the Elasticsearch response data used for Autosuggest. + * + * @hook ep.Autosuggest.data + * @since 4.4.0 + * + * @param {object} data Response data. + * @returns {object} Response data. + */ + return applyFilters('ep.Autosuggest.data', data, searchTerm); } catch (error) { // eslint-disable-next-line no-console console.error(error); @@ -217,11 +226,10 @@ async function esSearch(query, searchTerm) { * @returns {boolean} return true */ function updateAutosuggestBox(options, input) { - let i; - let itemString = ''; + let listHTML = ''; // get the search term for use later on - const { value } = input; + const { value: searchText } = input; const container = findAncestorByClass(input, 'ep-autosuggest-container'); const resultsContainer = container.querySelector('.ep-autosuggest'); const suggestList = resultsContainer.querySelector('.autosuggest-list'); @@ -244,12 +252,13 @@ function updateAutosuggestBox(options, input) { // create markup for list items // eslint-disable-next-line - for ( i = 0; resultsLimit > i; ++i ) { - const text = options[i]._source.post_title; - const url = options[i]._source.permalink; + for ( let index = 0; resultsLimit > index; ++index ) { + const option = options[index]; + const text = option._source.post_title; + const url = option._source.permalink; const escapedText = escapeDoubleQuotes(text); - const searchParts = value.trim().split(' '); + const searchParts = searchText.trim().split(' '); let resultsText = escapedText; if (epas.highlightingEnabled) { @@ -262,25 +271,44 @@ function updateAutosuggestBox(options, input) { ); } - let itemHTML = `
  • + let itemHTML = `
  • ${resultsText}
  • `; - if (typeof window.epAutosuggestItemHTMLFilter !== 'undefined') { - itemHTML = window.epAutosuggestItemHTMLFilter(itemHTML, options[i], i, value); - } + /** + * Filter the HTML for an Autosuggest suggestion. + * + * @hook ep.Autosuggest.itemHTML + * @since 4.4.0 + * + * @param {string} itemHTML Item HTML. + * @param {object} option Elasticsearch record for suggestion. + * @param {number} index Suggestion index. + * @param {string} searchText Search term. + * @returns {string} Item HTML. + */ + itemHTML = applyFilters('ep.Autosuggest.itemHTML', itemHTML, option, index, searchText); - itemString += itemHTML; + listHTML += itemHTML; } - if (typeof window.epAutosuggestListItemsHTMLFilter !== 'undefined') { - itemString = window.epAutosuggestListItemsHTMLFilter(itemString, options, input); - } + /** + * Filter the HTML for the list of Autosuggest suggestions. + * + * @hook ep.Autosuggest.listHTML + * @since 4.4.0 + * + * @param {string} listHTML List HTML. + * @param {object[]} options Elasticsearch records for suggestions. + * @param {Element} input Input element that triggered Autosuggest. + * @returns {string} List HTML. + */ + listHTML = applyFilters('ep.Autosuggest.listHTML', listHTML, options, input); // append list items to the list - suggestList.innerHTML = itemString; + suggestList.innerHTML = listHTML; const autosuggestItems = Array.from(document.querySelectorAll('.autosuggest-link')); @@ -552,9 +580,9 @@ function init() { let query = buildSearchQuery(searchText, placeholder, queryJSON); - if (postTypes.length > 0) { - query = JSON.parse(query); + query = JSON.parse(query); + if (postTypes.length > 0) { if (typeof query.post_filter.bool.must !== 'undefined') { query.post_filter.bool.must.push({ terms: { @@ -562,16 +590,21 @@ function init() { }, }); } - - query = JSON.stringify(query); } - // Allow filtering the search query based on the input. - if (typeof window.epAutosuggestQueryFilter !== 'undefined') { - query = JSON.stringify( - window.epAutosuggestQueryFilter(JSON.parse(query), searchText, input), - ); - } + /** + * Filter the Elasticsearch query used for Autosuggest. + * + * @hook ep.Autosuggest.query + * @since 4.4.0 + * + * @param {object} query Elasticsearch query. + * @param {string} searchText Search term. + * @param {Element} input Input element that triggered Autosuggest. + * @returns {object} Elasticsearch query. + */ + query = applyFilters('ep.Autosuggest.query', query, searchText, input); + query = JSON.stringify(query); // fetch the results const response = await esSearch(query, searchText); @@ -643,10 +676,10 @@ function init() { /** * Insert an autosuggest list after an element. * - * @param {Element} element Element to add the autosuggest list after. + * @param {Element} previousElement Element to add the autosuggest list after. * @returns {void} */ - const insertAutosuggestElement = (element) => { + const insertAutosuggestElement = (previousElement) => { if (!autosuggestElement) { autosuggestElement = document.createElement('div'); autosuggestElement.classList.add('ep-autosuggest'); @@ -659,13 +692,21 @@ function init() { autosuggestElement.appendChild(autosuggestList); } - let clonedElement = autosuggestElement.cloneNode(true); + let element = autosuggestElement.cloneNode(true); - if (typeof window.epAutosuggestElementFilter !== 'undefined') { - clonedElement = window.epAutosuggestElementFilter(clonedElement, element); - } + /** + * Filter the Autosuggest container element before it is inserted. + * + * @hook ep.Autosuggest.element + * @since 4.4.0 + * + * @param {Element} element Autosuggest container element. + * @param {Element} previousElement Element the container will be inserted after. + * @returns {Element} Autosuggest container element. + */ + element = applyFilters('ep.Autosuggest.element', element, previousElement); - element.insertAdjacentElement('afterend', clonedElement); + previousElement.insertAdjacentElement('afterend', element); }; /** From b9f93448cb78f74a367af0bc412cf88f23ea6f97 Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Tue, 13 Sep 2022 18:46:21 +1000 Subject: [PATCH 3/5] Update documentation. --- assets/js/autosuggest/index.js | 1 + docs/theme-integration.md | 93 +++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/assets/js/autosuggest/index.js b/assets/js/autosuggest/index.js index b70728c1ea..a4f42f11e6 100644 --- a/assets/js/autosuggest/index.js +++ b/assets/js/autosuggest/index.js @@ -208,6 +208,7 @@ async function esSearch(query, searchTerm) { * @since 4.4.0 * * @param {object} data Response data. + * @param {string} searchTerm Search term. * @returns {object} Response data. */ return applyFilters('ep.Autosuggest.data', data, searchTerm); diff --git a/docs/theme-integration.md b/docs/theme-integration.md index 4af6993da7..ae44742809 100644 --- a/docs/theme-integration.md +++ b/docs/theme-integration.md @@ -36,47 +36,57 @@ You could display the loading gif while suggestions are being fetched with this ### Customize Suggestion Markup -When ElasticPress Autosuggest renders the list of suggestions, each item is run through a `window.epAutosuggestItemHTMLFilter()` function (if this function is defined). Defining this function in your theme (or a plugin, if appropriate) enables you to customize the markup for suggestions and add or remove fields to be displayed in the suggestion. +The HTML for individual Autosuggest suggestions are filtered by the `ep.Autosuggest.itemHTML` [JavaScript hook](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-hooks/). You can add or remove fields on suggestions by adding a filter to this hook from your theme (or a plugin, if appropriate). -The `epAutosuggestItemHTMLFilter()` function must return the HTML for the suggestion as a string, and accept 4 parameters: +Filters for the `ep.Autosuggest.itemHTML` hook receive the original HTML as a string and 3 additional values related to the suggestion as parameters: -1. `itemHTML` _(string)_ The suggestion HTML as a string. -2. `option` _(object)_ The Elasticsearch record for the suggestion. -3. `i` _(int)_ The index of the suggestion in the results set. -4. `searchText` _(string)_ The search term. +| Name | Type | Description +| -------------|--------|------------------------------------------------ +| `itemHTML` | string | The suggestion HTML as a string. +| `option` | object | The Elasticsearch record for the suggestion. +| `index` | number | The index of the suggestion in the results set. +| `searchText` | string | The search term. -This example uses the function to add the post date to the suggestion: +Filters must return HTML for the suggestion as a string. + +This example uses the hook to add the post date to the suggestion: ``` -window.epAutosuggestItemHTMLFilter = (itemHTML, option, i, searchText) => { +const autosuggestItemHTML = (itemHTML, option, index, searchText) => { const text = option._source.post_title; const url = option._source.permalink; const postDate = new Date(option._source.post_date).toLocaleString('en', { dateStyle: 'medium' }) - return `
  • + return `
  • ${text} (${postDate})
  • `; -} +}; + +wp.hooks.addFilter('ep.Autosuggest.itemHTML', 'myTheme/autosuggestItemHTML', autosuggestItemHTML); ``` Note that the `class`, `id`, `role`, `aria-selected`, `data-url`, and `tabindex` attributes in the returned markup must match the default values for those attributes, as they do in the example, to ensure that Autosuggest functions as normal. ### Customize Suggestions List Markup -ElasticPress Autosuggest enables customization of the entire suggestions list using the `window.epAutosuggestListItemsHTMLFilter()` function, (if this function is defined). By defining this function in your theme (or a plugin, if appropriate), you can append or prepend items to the suggestions list, or otherwise make edits to the entire list (rather than individual suggestions). +The HTML for the entire list of suggestions is filtered by the `ep.Autosuggest.listHTML` [JavaScript hook](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-hooks/). You can append or prepend items to the suggestions list, or otherwise make edits to the entire list (rather than individual suggestions), by adding a filter to this hook from your theme (or a plugin, if appropriate). + +Filters for the `ep.Autosuggest.listHTML` hook receive the original HTML as a string and 2 additional values related to the suggestion as parameters: -The `epAutosuggestListItemsHTMLFilter()` function must return the HTML for the suggestions list as a string, and accept 3 parameters: +| Name | Type | Description +| -------------|---------|----------------------------------------------- +| `listHTML` | string | The list HTML as a string. +| `options` | array | The Elasticsearch records for the suggestions. +| `input` | Element | The input element that triggered Autosuggest. -1. `listItemsHTML` _(string)_ The list items HTML as a string. -2. `options` _(array)_ The Elasticsearch records for all of the suggestions being listed. -3. `input` _(Element)_ The DOM element of the input that triggered Autosuggest. +Filters must return HTML for the suggestion list as a string. -This example uses the function to add a "View All Results" link to the bottom of the suggestions list. +This example uses the hook to add a "View All Results" link to the bottom of the suggestions list. ``` -window.epAutosuggestListItemsHTMLFilter = (listItemsHTML, options, input) => { +const autosuggestListHTMLFilter = (listHTML, options, input) => { const allUrl = new URL(input.form.action); const formData = new FormData(input.form); const urlParams = new URLSearchParams(formData); @@ -85,31 +95,37 @@ window.epAutosuggestListItemsHTMLFilter = (listItemsHTML, options, input) => { const url = allUrl.toString(); - listItemsHTML += `
  • + listHTML += `
  • View All Results
  • `; - return listItemsHTML; -} + return listHTML; +}; + +wp.hooks.addFilter('ep.Autosuggest.listHTML', 'myTheme/autosuggestListHTMLFilter', autosuggestListHTMLFilter); ``` Note that the `class`, `role`, `aria-selected`, and `tabindex` attributes in any new items must match the default values for those attributes, as they do in the example, to ensure that Autosuggest functions as normal. Items must also contain a link with the `href` and `data-url` attributes set to the URL that the item should link to. ### Customize the Suggestions Container -Before ElasticPress inserts the markup for Autosuggest into the search form the element to be added is run through a `window.epAutosuggestElementFilter()` function (if this function is defined). This function enables you to modify the markup of the Autosuggest container by defining this function in your theme (or a plugin, if appropriate). +When ElasticPress inserts the markup for Autosuggest into the search form the element to be added is filtered by the `ep.Autosuggest.element` [JavaScript hook](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-hooks/). You can modify the markup of the Autosuggest container by adding a filter to this hook from your theme (or a plugin, if appropriate). + +Filters for the `ep.Autosuggest.element` hook receive the DOM element that will be inserted and the element it will be inserted after as parameters: -The `epAutosuggestElementFilter()` function must return a DOM element, and accept 2 parameters: +| Name | Type | Description +| -----------------------|---------|----------------------------------------------- +| `element` | Element | The autosuggest container element. +| `inpreviousElementput` | Element | The element the container will be inserted after. -1. `element` _(Element)_ The DOM element being inserted. -2. `input` _(Element)_ The DOM element Autosuggest is being inserted after. +Filters must return a DOM element for the Autosuggest container. -This example uses the function to add a "Powered by ElasticPress" message to the Autosuggest dropdown. +This example uses the hook to add a "Powered by ElasticPress" message to the Autosuggest dropdown. ``` -window.epAutosuggestElementFilter = (element, input) => { +const autosuggestElementFilter = (element, previousElementput) => { const poweredBy = document.createElement('div'); poweredBy.textContent = 'Powered by ElasticPress'; @@ -117,23 +133,29 @@ window.epAutosuggestElementFilter = (element, input) => { element.appendChild(poweredBy); return element; -} +}; + +wp.hooks.addFilter('ep.Autosuggest.element', 'myTheme/autosuggestElementFilter', autosuggestElementFilter); ``` ### Customize the Autosuggest Query -To get suggestions for Autosuggest, ElasticPress sends an AJAX request containing an Elasticsearch query to your Autosuggest endpoint. This request can be modified prior to sending via the `window.epAutosuggestQueryFilter()` function (if this function is defined) in order to customize or enhance the request with additional client-side data. +To get suggestions for Autosuggest, ElasticPress sends an AJAX request containing an Elasticsearch query to your Autosuggest endpoint. Before this request is sent the Elasticsearch query is filtered by the `ep.Autosuggest.query` [JavaScript hook](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-hooks/). You can modify the query by adding a filter to this hook from your theme (or a plugin, if appropriate). + +Filters for the `ep.Autosuggest.query` hook receive the query as a JavaScript object and 2 additional values related to the request as parameters: -The `epAutosuggestQueryFilter()` function must return a JavaScript object representing the query, and accept 3 parameters: +| Name | Type | Description +| -------------|---------|------------------------------------------------ +| `query` | object | The Elasticsearch query as a JavaScript object. +| `searchText` | string | The search term. +| `input` | Element | The input element that triggered Autosuggest. -1. `query` _(Object)_ The Elasticsearch query as a JavaScript object. -2. `searchText` _(string)_ The search term. -2. `input` _(Element)_ The DOM element of the input that triggered Autosuggest. +Filters must return an Elasticsearch query as an object. This example uses the function to add the value of a `wp_dropdown_categories()` field as a filter to the search query: ``` -window.epAutosuggestQueryFilter = (query, searchText, input) => { +const autosuggestQueryFilter = (query, searchText, input) => { const formData = new FormData(input.form); const category = formData.get('cat'); @@ -146,9 +168,10 @@ window.epAutosuggestQueryFilter = (query, searchText, input) => { } return query; -} -``` +}; +wp.hooks.addFilter('ep.Autosuggest.query', 'myTheme/autosuggestQueryFilter', autosuggestQueryFilter); +``` ## Instant Results From 8096c33000824ff66cbcc3963cac39af57c991ee Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Sat, 17 Sep 2022 01:34:51 +1000 Subject: [PATCH 4/5] Update hook docs. --- .eslintrc.js | 6 ++++++ assets/js/autosuggest/index.js | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ceca853366..48c662675e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,4 +2,10 @@ const defaultEslintrc = require('10up-toolkit/config/.eslintrc'); module.exports = { ...defaultEslintrc, + 'jsdoc/check-tag-names': [ + 'error', + { + definedTags: ['filter', 'action'], + }, + ], }; diff --git a/assets/js/autosuggest/index.js b/assets/js/autosuggest/index.js index a4f42f11e6..154d71b85d 100644 --- a/assets/js/autosuggest/index.js +++ b/assets/js/autosuggest/index.js @@ -1,4 +1,4 @@ -/* eslint-disable camelcase, no-underscore-dangle, no-use-before-define, jsdoc/check-tag-names */ +/* eslint-disable camelcase, no-underscore-dangle, no-use-before-define */ /** * WordPress dependencies. @@ -204,8 +204,8 @@ async function esSearch(query, searchTerm) { /** * Filter the Elasticsearch response data used for Autosuggest. * - * @hook ep.Autosuggest.data - * @since 4.4.0 + * @filter ep.Autosuggest.data + * @since 4.3.1 * * @param {object} data Response data. * @param {string} searchTerm Search term. @@ -281,8 +281,8 @@ function updateAutosuggestBox(options, input) { /** * Filter the HTML for an Autosuggest suggestion. * - * @hook ep.Autosuggest.itemHTML - * @since 4.4.0 + * @filter ep.Autosuggest.itemHTML + * @since 4.3.1 * * @param {string} itemHTML Item HTML. * @param {object} option Elasticsearch record for suggestion. @@ -298,8 +298,8 @@ function updateAutosuggestBox(options, input) { /** * Filter the HTML for the list of Autosuggest suggestions. * - * @hook ep.Autosuggest.listHTML - * @since 4.4.0 + * @filter ep.Autosuggest.listHTML + * @since 4.3.1 * * @param {string} listHTML List HTML. * @param {object[]} options Elasticsearch records for suggestions. @@ -596,8 +596,8 @@ function init() { /** * Filter the Elasticsearch query used for Autosuggest. * - * @hook ep.Autosuggest.query - * @since 4.4.0 + * @filter ep.Autosuggest.query + * @since 4.3.1 * * @param {object} query Elasticsearch query. * @param {string} searchText Search term. @@ -698,8 +698,8 @@ function init() { /** * Filter the Autosuggest container element before it is inserted. * - * @hook ep.Autosuggest.element - * @since 4.4.0 + * @filter ep.Autosuggest.element + * @since 4.3.1 * * @param {Element} element Autosuggest container element. * @param {Element} previousElement Element the container will be inserted after. From fbd4760548708fc5429aa668cbaa25cbb581b126 Mon Sep 17 00:00:00 2001 From: Jacob Peattie Date: Sat, 17 Sep 2022 02:15:06 +1000 Subject: [PATCH 5/5] Fix ESLint config. --- .eslintrc.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 48c662675e..f7ab578a0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,10 +2,13 @@ const defaultEslintrc = require('10up-toolkit/config/.eslintrc'); module.exports = { ...defaultEslintrc, - 'jsdoc/check-tag-names': [ - 'error', - { - definedTags: ['filter', 'action'], - }, - ], + rules: { + ...defaultEslintrc.rules, + 'jsdoc/check-tag-names': [ + 'error', + { + definedTags: ['filter', 'action'], + }, + ], + }, };