From 6819f1d206259decf74094ae4a84c40be2a8a686 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Thu, 9 Jan 2025 10:54:51 -0300 Subject: [PATCH 01/23] Search Templates feature --- .babelrc | 5 + .husky/pre-commit | 3 - .../apps/search-templates-list.js | 70 +++ .../components/new-template-row.js | 60 ++ .../components/template-field.js | 80 +++ .../components/template-row.js | 152 +++++ assets/js/search-templates/config.js | 7 + assets/js/search-templates/index.js | 46 ++ assets/js/search-templates/provider.js | 116 ++++ assets/js/search-templates/style.css | 3 + assets/js/settings-screen/index.js | 72 +++ assets/js/settings-screen/style.css | 27 + includes/classes/Feature/ExternalContent.php | 2 +- includes/classes/Feature/SearchTemplates.php | 202 +++++++ includes/classes/REST/SearchTemplates.php | 191 +++++++ includes/functions/utils.php | 25 + package-lock.json | 519 ++++++++++++++++-- package.json | 8 +- 18 files changed, 1550 insertions(+), 38 deletions(-) create mode 100755 .babelrc create mode 100644 assets/js/search-templates/apps/search-templates-list.js create mode 100644 assets/js/search-templates/components/new-template-row.js create mode 100644 assets/js/search-templates/components/template-field.js create mode 100644 assets/js/search-templates/components/template-row.js create mode 100644 assets/js/search-templates/config.js create mode 100644 assets/js/search-templates/index.js create mode 100644 assets/js/search-templates/provider.js create mode 100644 assets/js/search-templates/style.css create mode 100644 assets/js/settings-screen/index.js create mode 100644 assets/js/settings-screen/style.css create mode 100644 includes/classes/Feature/SearchTemplates.php create mode 100644 includes/classes/REST/SearchTemplates.php diff --git a/.babelrc b/.babelrc new file mode 100755 index 0000000..48f7e45 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "@10up/babel-preset-default" + ] +} diff --git a/.husky/pre-commit b/.husky/pre-commit index d24fdfc..2312dc5 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - npx lint-staged diff --git a/assets/js/search-templates/apps/search-templates-list.js b/assets/js/search-templates/apps/search-templates-list.js new file mode 100644 index 0000000..c1f901e --- /dev/null +++ b/assets/js/search-templates/apps/search-templates-list.js @@ -0,0 +1,70 @@ +/** + * WordPress Dependencies. + */ +import { createInterpolateElement, WPElement } from '@wordpress/element'; +import { Panel, Spinner } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies. + */ +import { useSearchTemplate } from '../provider'; +import TemplateRow from '../components/template-row'; +import NewTemplateRow from '../components/new-template-row'; +import { endpointExample, searchApiDocUrl } from '../config'; + +/** + * Search Templates app. + * + * @returns {WPElement} App element. + */ +export default () => { + const { isLoading, templates } = useSearchTemplate(); + + return ( + <> +

+ {createInterpolateElement( + __( + 'Search templates are Elasticsearch queries stored in ElasticPress.io servers used by the Search API. Please note that all the API fields are still available for custom search templates. Your templates do not to differ in post types, offset, pagination arguments, or even filters, as for those you can still use query parameters. The templates can be used for searching in different fields or applying different scores, for instance.', + 'elasticpress-labs', + ), + { a: }, // eslint-disable-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label + )} +

+

+ {createInterpolateElement( + sprintf( + __( + 'Once you have a search template saved, you can start sending requests to your endpoint URL below. Your template needs to have {{ep_placeholder}} in all places where the search term needs to be used.', + 'elasticpress-labs', + ), + endpointExample, + ), + { code: }, + )} +

+

+ {createInterpolateElement( + sprintf(__('Endpoint URL: %s'), endpointExample), + { strong: , code: }, + )} +

+ + {isLoading ? ( +
+ {__('Loading...', 'elasticpress-labs')} + +
+ ) : ( + <> + {Object.keys(templates).map((templateName) => ( + + ))} + + + )} +
+ + ); +}; diff --git a/assets/js/search-templates/components/new-template-row.js b/assets/js/search-templates/components/new-template-row.js new file mode 100644 index 0000000..169ba1f --- /dev/null +++ b/assets/js/search-templates/components/new-template-row.js @@ -0,0 +1,60 @@ +/** + * WordPress Dependencies. + */ +import { Button, Flex, PanelBody, PanelRow, TextControl } from '@wordpress/components'; +import { useState, WPElement } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies. + */ +import { useSearchTemplateDispatch } from '../provider'; +import TemplateField from './template-field'; + +/** + * New Template Row component. + * + * @returns {WPElement} + */ +export default () => { + const [name, setName] = useState(''); + const [template, setTemplate] = useState(''); + + const { saveTemplate } = useSearchTemplateDispatch(); + + const onAddNewTemplate = () => { + saveTemplate(name, template); + setName(''); + setTemplate(''); + }; + + return ( + + + + + + + + + + + + ); +}; diff --git a/assets/js/search-templates/components/template-field.js b/assets/js/search-templates/components/template-field.js new file mode 100644 index 0000000..330c8fc --- /dev/null +++ b/assets/js/search-templates/components/template-field.js @@ -0,0 +1,80 @@ +/** + * WordPress Dependencies. + */ +import { BaseControl, Button, Flex, Notice } from '@wordpress/components'; +import { createInterpolateElement, WPElement } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies. + */ +import { defaultTemplate } from '../config'; + +/** + * Template Field component. + * + * @param {object} props Component props. + * @param {string} props.value The search template as a string. + * @param {Function} props.onChange Function to be executed when the value changes. + * @param {boolean} props.disabled If the field should be enabled or not. + * @returns {WPElement} + */ +export default ({ value, onChange, disabled }) => { + const isValueValidJson = () => { + if (!value) { + return true; + } + + try { + return JSON.parse(value) && !!value; + } catch (e) { + return false; + } + }; + + return ( + {{ep_placeholder}}
, so it can be replaced by the actual search term.', + 'elasticpress-labs', + ), + { code: }, + )} + > + {isValueValidJson() || ( + + {__('This does not seem to be a valid JSON object.', 'elasticpress-labs')} + + )} + + + {__('Template', 'elasticpress-labs')} + + {defaultTemplate && ( + + )} + +