From 80ef950499bd1cc2db9e2b84fe80f6455bc4d13e Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 5 Mar 2024 10:52:29 -0800 Subject: [PATCH 1/6] feat: Prototype search UI using Instantsearch + Meilisearch --- package-lock.json | 281 +++++++++++++++++++++++++++++++++++++ package.json | 3 + src/header/Header.jsx | 43 +++--- src/header/SearchModal.tsx | 66 +++++++++ src/header/SearchUI.tsx | 59 ++++++++ src/header/messages.js | 5 + src/hooks.js | 16 +++ 7 files changed, 457 insertions(+), 16 deletions(-) create mode 100644 src/header/SearchModal.tsx create mode 100644 src/header/SearchUI.tsx diff --git a/package-lock.json b/package-lock.json index 85899f669d..de07532759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/react-fontawesome": "0.2.0", + "@meilisearch/instant-meilisearch": "^0.16.0", "@openedx-plugins/course-app-calculator": "file:plugins/course-apps/calculator", "@openedx-plugins/course-app-edxnotes": "file:plugins/course-apps/edxnotes", "@openedx-plugins/course-app-learning_assistant": "file:plugins/course-apps/learning_assistant", @@ -45,6 +46,7 @@ "email-validator": "2.0.4", "file-saver": "^2.0.5", "formik": "2.2.6", + "instantsearch.css": "^8.1.0", "jszip": "^3.10.1", "lodash": "4.17.21", "moment": "2.29.4", @@ -53,6 +55,7 @@ "react-datepicker": "^4.13.0", "react-dom": "17.0.2", "react-helmet": "^6.1.0", + "react-instantsearch-dom": "^6.40.4", "react-redux": "7.2.9", "react-responsive": "9.0.2", "react-router": "6.16.0", @@ -106,6 +109,140 @@ "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", "dev": true }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", + "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", + "peer": true, + "dependencies": { + "@algolia/cache-common": "4.22.1" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", + "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", + "peer": true + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", + "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", + "peer": true, + "dependencies": { + "@algolia/cache-common": "4.22.1" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", + "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", + "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", + "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", + "peer": true, + "dependencies": { + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", + "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", + "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + }, + "node_modules/@algolia/logger-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", + "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", + "peer": true + }, + "node_modules/@algolia/logger-console": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", + "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", + "peer": true, + "dependencies": { + "@algolia/logger-common": "4.22.1" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", + "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", + "peer": true, + "dependencies": { + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", + "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", + "peer": true + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", + "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", + "peer": true, + "dependencies": { + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", + "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", + "peer": true, + "dependencies": { + "@algolia/cache-common": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/requester-common": "4.22.1" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -4785,6 +4922,14 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@meilisearch/instant-meilisearch": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@meilisearch/instant-meilisearch/-/instant-meilisearch-0.16.0.tgz", + "integrity": "sha512-JdqG/Wq+8cbzwxz4DKLuUuTetpiAcpaGQaOvi2wJzzXyYfyoiGh3/F12XMY8CA/pfDRLZtrZpDYvYLcsH+QUqw==", + "dependencies": { + "meilisearch": "^0.37.0" + } + }, "node_modules/@newrelic/publish-sourcemap": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@newrelic/publish-sourcemap/-/publish-sourcemap-5.1.0.tgz", @@ -6654,6 +6799,39 @@ "ajv": "^6.9.1" } }, + "node_modules/algoliasearch": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", + "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", + "peer": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.22.1", + "@algolia/cache-common": "4.22.1", + "@algolia/cache-in-memory": "4.22.1", + "@algolia/client-account": "4.22.1", + "@algolia/client-analytics": "4.22.1", + "@algolia/client-common": "4.22.1", + "@algolia/client-personalization": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/logger-console": "4.22.1", + "@algolia/requester-browser-xhr": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/requester-node-http": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.14.0.tgz", + "integrity": "sha512-gXDXzsSS0YANn5dHr71CUXOo84cN4azhHKUbg71vAWnH+1JBiR4jf7to3t3JHXknXkbV0F7f055vUSBKrltHLQ==", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -8436,6 +8614,14 @@ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -12190,6 +12376,11 @@ "node": ">=12.0.0" } }, + "node_modules/instantsearch.css": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/instantsearch.css/-/instantsearch.css-8.1.0.tgz", + "integrity": "sha512-rPhcAZ02bLwUn3iOXbldZW/yl+17guWoH3qWYZ8nQEwNBx5+wZ6Bv8mFqqK448+R2aU4nbFKIhmoTIPXI5Zobg==" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -14853,6 +15044,14 @@ "node": ">= 0.6" } }, + "node_modules/meilisearch": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.37.0.tgz", + "integrity": "sha512-LdbK6JmRghCawrmWKJSEQF0OiE82md+YqJGE/U2JcCD8ROwlhTx0KM6NX4rQt0u0VpV0QZVG9umYiu3CSSIJAQ==", + "dependencies": { + "cross-fetch": "^3.1.6" + } + }, "node_modules/memfs": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", @@ -15295,6 +15494,44 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -17452,6 +17689,50 @@ "react-is": "^16.13.1" } }, + "node_modules/react-instantsearch-core": { + "version": "6.40.4", + "resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.40.4.tgz", + "integrity": "sha512-sEOgRU2MKL8edO85sNHvKlZ5yq9OFw++CDsEqYpHJvbWLE/2J2N49XAUY90kior09I2kBkbgowBbov+Py1AubQ==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "algoliasearch-helper": "3.14.0", + "prop-types": "^15.6.2", + "react-fast-compare": "^3.0.0" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 5", + "react": ">= 16.3.0 < 19" + } + }, + "node_modules/react-instantsearch-core/node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-instantsearch-dom": { + "version": "6.40.4", + "resolved": "https://registry.npmjs.org/react-instantsearch-dom/-/react-instantsearch-dom-6.40.4.tgz", + "integrity": "sha512-Oy8EKEOg/dfTE8tHc7GZRlzUdbZY4Mxas1x2OtvSNui+YAbIWafIf1g98iOGyVTB2qI5WH91YyUJTLPNfLrs6Q==", + "deprecated": "package has moved to react-instantsearch", + "dependencies": { + "@babel/runtime": "^7.1.2", + "algoliasearch-helper": "3.14.0", + "classnames": "^2.2.5", + "prop-types": "^15.6.2", + "react-fast-compare": "^3.0.0", + "react-instantsearch-core": "6.40.4" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 5", + "react": ">= 16.3.0 < 19", + "react-dom": ">= 16.3.0 < 19" + } + }, + "node_modules/react-instantsearch-dom/node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-intl": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.2.tgz", diff --git a/package.json b/package.json index 8b4080ece2..742ff64e4b 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/react-fontawesome": "0.2.0", + "@meilisearch/instant-meilisearch": "^0.16.0", "@openedx-plugins/course-app-calculator": "file:plugins/course-apps/calculator", "@openedx-plugins/course-app-edxnotes": "file:plugins/course-apps/edxnotes", "@openedx-plugins/course-app-learning_assistant": "file:plugins/course-apps/learning_assistant", @@ -72,6 +73,7 @@ "email-validator": "2.0.4", "file-saver": "^2.0.5", "formik": "2.2.6", + "instantsearch.css": "^8.1.0", "jszip": "^3.10.1", "lodash": "4.17.21", "moment": "2.29.4", @@ -80,6 +82,7 @@ "react-datepicker": "^4.13.0", "react-dom": "17.0.2", "react-helmet": "^6.1.0", + "react-instantsearch-dom": "^6.40.4", "react-redux": "7.2.9", "react-responsive": "9.0.2", "react-router": "6.16.0", diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 6cb4147f04..4e351aaaad 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -1,11 +1,14 @@ +// @ts-check import React from 'react'; import PropTypes from 'prop-types'; import { getConfig } from '@edx/frontend-platform'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils'; import messages from './messages'; +import SearchModal from './SearchModal'; +import { useKeyHandler } from '../hooks'; const Header = ({ courseId, @@ -13,9 +16,12 @@ const Header = ({ courseNumber, courseTitle, isHiddenMainMenu, - // injected - intl, }) => { + const intl = useIntl(); + const [showSearchModal, setShowSearchModal] = React.useState(false); + const toggleModal = React.useCallback(() => setShowSearchModal(x => !x), []); + useKeyHandler({ handler: toggleModal, keyName: '/' }); + const studioBaseUrl = getConfig().STUDIO_BASE_URL; const mainMenuDropdowns = [ { @@ -36,16 +42,23 @@ const Header = ({ ]; const outlineLink = `${studioBaseUrl}/course/${courseId}`; return ( - + <> + + + ); }; @@ -55,8 +68,6 @@ Header.propTypes = { courseOrg: PropTypes.string, courseTitle: PropTypes.string, isHiddenMainMenu: PropTypes.bool, - // injected - intl: intlShape.isRequired, }; Header.defaultProps = { @@ -67,4 +78,4 @@ Header.defaultProps = { isHiddenMainMenu: false, }; -export default injectIntl(Header); +export default Header; diff --git a/src/header/SearchModal.tsx b/src/header/SearchModal.tsx new file mode 100644 index 0000000000..99ea97ef18 --- /dev/null +++ b/src/header/SearchModal.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { + ModalDialog, +} from '@openedx/paragon'; +import { getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { useQuery } from '@tanstack/react-query'; + +import messages from './messages'; +import { LoadingSpinner } from '../generic/Loading'; +import SearchUI from './SearchUI'; + +interface Props { + courseId: string; + isOpen: boolean; + onClose: () => void; +} + + +const SearchModal: React.FC = ({courseId, ...props}) => { + const intl = useIntl(); + + const { + data: searchEndpointData, + isLoading + } = useQuery({ + queryKey: ['content_search'], + queryFn: async () => { + const url = new URL('api/content_search/v2/studio/', getConfig().STUDIO_BASE_URL).href; + const response = await getAuthenticatedHttpClient().get(url); + return { + url: response.data.url as string, + indexName: response.data.index_name as string, + apiKey: response.data.api_key as string, + }; + }, + staleTime: 60 * 60, // If cache is up to one hour old, no need to re-fetch + refetchOnWindowFocus: false, // This doesn't need to be refreshed when the user switches back to this tab. + }); + + return ( + + + {intl.formatMessage(messages.courseSearchTitle)} + + + { + searchEndpointData ? <> + + + : (isLoading ? : <>Error) + } + + + ); +}; + +export default SearchModal; diff --git a/src/header/SearchUI.tsx b/src/header/SearchUI.tsx new file mode 100644 index 0000000000..f955df08e1 --- /dev/null +++ b/src/header/SearchUI.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { + InstantSearch, + InfiniteHits, + HierarchicalMenu, + SearchBox, + Stats, + Highlight +} from "react-instantsearch-dom"; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { instantMeiliSearch } from "@meilisearch/instant-meilisearch"; +import "instantsearch.css/themes/algolia-min.css"; + +interface Props { + url: string; + apiKey: string; + indexName: string; +} + + +const SearchUI: React.FC = (props) => { + const intl = useIntl(); + + const {searchClient} = React.useMemo(() => instantMeiliSearch(props.url, props.apiKey), [props.url, props.apiKey]); + + return ( +
+ + + + + + +
+ ); +}; + +const Hit = ({ hit }) => ( +
+
+ +
+

+
+ + +
+
+); + +export default SearchUI; diff --git a/src/header/messages.js b/src/header/messages.js index 794d83743a..9616faf314 100644 --- a/src/header/messages.js +++ b/src/header/messages.js @@ -156,6 +156,11 @@ const messages = defineMessages({ defaultMessage: 'Back to course outline in Studio', description: 'The aria label for the link back to the Studio Course Outline', }, + courseSearchTitle: { + id: 'courseSearch.title', + defaultMessage: 'Search Course(s)', + description: 'Title for the course search dialog', + }, }); export default messages; diff --git a/src/hooks.js b/src/hooks.js index 8c649ea06b..bb2e949b0f 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -32,3 +32,19 @@ export const useEscapeClick = ({ onEscape, dependency }) => { }; }, [dependency]); }; + +export const useKeyHandler = ({ keyName, handler, dependencies = [] }) => { + useEffect(() => { + const checkKey = (event) => { + if (event.key === keyName) { + handler(); + } + }; + + window.addEventListener('keydown', checkKey); + + return () => { + window.removeEventListener('keydown', checkKey); + }; + }, dependencies); +}; From ed28225261ee97d1184f0c8cacbb69de8b225865 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 5 Mar 2024 12:08:38 -0800 Subject: [PATCH 2/6] feat: Refine by XBlock type too --- src/header/SearchModal.tsx | 2 ++ src/header/SearchUI.tsx | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/header/SearchModal.tsx b/src/header/SearchModal.tsx index 99ea97ef18..94bd0af739 100644 --- a/src/header/SearchModal.tsx +++ b/src/header/SearchModal.tsx @@ -21,6 +21,8 @@ interface Props { const SearchModal: React.FC = ({courseId, ...props}) => { const intl = useIntl(); + // Load the Meilisearch connection details from the LMS: the URL to use, the index name, and an API key specific + // to us (to the current user) that allows us to search all content we have permission to view. const { data: searchEndpointData, isLoading diff --git a/src/header/SearchUI.tsx b/src/header/SearchUI.tsx index f955df08e1..a2f9b1ab66 100644 --- a/src/header/SearchUI.tsx +++ b/src/header/SearchUI.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { - InstantSearch, - InfiniteHits, HierarchicalMenu, + Highlight, + InfiniteHits, + InstantSearch, + RefinementList, SearchBox, Stats, - Highlight } from "react-instantsearch-dom"; import { useIntl } from '@edx/frontend-platform/i18n'; import { instantMeiliSearch } from "@meilisearch/instant-meilisearch"; @@ -28,6 +29,9 @@ const SearchUI: React.FC = (props) => { + Refine by component type: + + Refine by tag: Date: Fri, 15 Mar 2024 11:36:03 -0700 Subject: [PATCH 3/6] chore: update to match backend changes --- src/header/SearchUI.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/header/SearchUI.tsx b/src/header/SearchUI.tsx index a2f9b1ab66..b2152836f2 100644 --- a/src/header/SearchUI.tsx +++ b/src/header/SearchUI.tsx @@ -54,8 +54,8 @@ const Hit = ({ hit }) => (

- - + +
); From 130ab3937b7e262f6792bfe84e7c7d68e25182f9 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 15 Mar 2024 12:22:29 -0700 Subject: [PATCH 4/6] feat: display breadcrumbs --- src/header/SearchUI.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/header/SearchUI.tsx b/src/header/SearchUI.tsx index b2152836f2..0a0eb4a5fc 100644 --- a/src/header/SearchUI.tsx +++ b/src/header/SearchUI.tsx @@ -57,6 +57,7 @@ const Hit = ({ hit }) => ( +
{hit.breadcrumbs.map((bc, i) => {bc.display_name} {i !== hit.breadcrumbs.length - 1 ? '>' : ''} )}
); From 8a963a997220237d6ff2b6c455290eee7a09252f Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 21 Mar 2024 17:40:44 -0700 Subject: [PATCH 5/6] chore: convert back to JSX --- package-lock.json | 43 ++++++++++-------- package.json | 1 + src/header/Header.jsx | 2 +- src/header/SearchModal.tsx | 68 ---------------------------- src/header/SearchUI.tsx | 64 -------------------------- src/search-modal/SearchModal.jsx | 75 +++++++++++++++++++++++++++++++ src/search-modal/SearchResult.jsx | 32 +++++++++++++ src/search-modal/SearchUI.jsx | 44 ++++++++++++++++++ src/search-modal/messages.js | 15 +++++++ 9 files changed, 193 insertions(+), 151 deletions(-) delete mode 100644 src/header/SearchModal.tsx delete mode 100644 src/header/SearchUI.tsx create mode 100644 src/search-modal/SearchModal.jsx create mode 100644 src/search-modal/SearchResult.jsx create mode 100644 src/search-modal/SearchUI.jsx create mode 100644 src/search-modal/messages.js diff --git a/package-lock.json b/package-lock.json index de07532759..cec5183918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,7 @@ "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.2.1", + "@types/react-instantsearch-dom": "^6.12.7", "axios": "^0.27.2", "axios-mock-adapter": "1.22.0", "eslint-import-resolver-webpack": "^0.13.8", @@ -113,7 +114,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", - "peer": true, "dependencies": { "@algolia/cache-common": "4.22.1" } @@ -121,14 +121,12 @@ "node_modules/@algolia/cache-common": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", - "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", - "peer": true + "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==" }, "node_modules/@algolia/cache-in-memory": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", - "peer": true, "dependencies": { "@algolia/cache-common": "4.22.1" } @@ -137,7 +135,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", - "peer": true, "dependencies": { "@algolia/client-common": "4.22.1", "@algolia/client-search": "4.22.1", @@ -148,7 +145,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", - "peer": true, "dependencies": { "@algolia/client-common": "4.22.1", "@algolia/client-search": "4.22.1", @@ -160,7 +156,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", - "peer": true, "dependencies": { "@algolia/requester-common": "4.22.1", "@algolia/transporter": "4.22.1" @@ -170,7 +165,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", - "peer": true, "dependencies": { "@algolia/client-common": "4.22.1", "@algolia/requester-common": "4.22.1", @@ -181,7 +175,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", - "peer": true, "dependencies": { "@algolia/client-common": "4.22.1", "@algolia/requester-common": "4.22.1", @@ -196,14 +189,12 @@ "node_modules/@algolia/logger-common": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", - "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", - "peer": true + "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==" }, "node_modules/@algolia/logger-console": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", - "peer": true, "dependencies": { "@algolia/logger-common": "4.22.1" } @@ -212,7 +203,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", - "peer": true, "dependencies": { "@algolia/requester-common": "4.22.1" } @@ -220,14 +210,12 @@ "node_modules/@algolia/requester-common": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", - "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", - "peer": true + "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==" }, "node_modules/@algolia/requester-node-http": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", - "peer": true, "dependencies": { "@algolia/requester-common": "4.22.1" } @@ -236,7 +224,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", - "peer": true, "dependencies": { "@algolia/cache-common": "4.22.1", "@algolia/logger-common": "4.22.1", @@ -6285,6 +6272,27 @@ "@types/react": "^17" } }, + "node_modules/@types/react-instantsearch-core": { + "version": "6.26.10", + "resolved": "https://registry.npmjs.org/@types/react-instantsearch-core/-/react-instantsearch-core-6.26.10.tgz", + "integrity": "sha512-izn21BqXtO3GA5Tx3x7SP6kfk1GJppkVdowuenKIOUj1sCJ3VHwoggsqVWv1DYVcsS8wydjR8Ra91XtI2a12rw==", + "dev": true, + "dependencies": { + "@types/react": "*", + "algoliasearch": ">=4", + "algoliasearch-helper": ">=3" + } + }, + "node_modules/@types/react-instantsearch-dom": { + "version": "6.12.7", + "resolved": "https://registry.npmjs.org/@types/react-instantsearch-dom/-/react-instantsearch-dom-6.12.7.tgz", + "integrity": "sha512-9PPKCOn0gnfMh+IdmM8WD3Ww1cwHbKeWsSddjI/lwLzs7SSSRh+UbYxo6RAM8kGw90EbDn0zTtrdAPl9RiizWQ==", + "dev": true, + "dependencies": { + "@types/react": "*", + "@types/react-instantsearch-core": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.33", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", @@ -6803,7 +6811,6 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", - "peer": true, "dependencies": { "@algolia/cache-browser-local-storage": "4.22.1", "@algolia/cache-common": "4.22.1", diff --git a/package.json b/package.json index 742ff64e4b..83df85c9a9 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.2.1", + "@types/react-instantsearch-dom": "^6.12.7", "axios": "^0.27.2", "axios-mock-adapter": "1.22.0", "eslint-import-resolver-webpack": "^0.13.8", diff --git a/src/header/Header.jsx b/src/header/Header.jsx index 4e351aaaad..b2f1744b21 100644 --- a/src/header/Header.jsx +++ b/src/header/Header.jsx @@ -7,7 +7,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils'; import messages from './messages'; -import SearchModal from './SearchModal'; +import SearchModal from '../search-modal/SearchModal'; import { useKeyHandler } from '../hooks'; const Header = ({ diff --git a/src/header/SearchModal.tsx b/src/header/SearchModal.tsx deleted file mode 100644 index 94bd0af739..0000000000 --- a/src/header/SearchModal.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { - ModalDialog, -} from '@openedx/paragon'; -import { getConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { useQuery } from '@tanstack/react-query'; - -import messages from './messages'; -import { LoadingSpinner } from '../generic/Loading'; -import SearchUI from './SearchUI'; - -interface Props { - courseId: string; - isOpen: boolean; - onClose: () => void; -} - - -const SearchModal: React.FC = ({courseId, ...props}) => { - const intl = useIntl(); - - // Load the Meilisearch connection details from the LMS: the URL to use, the index name, and an API key specific - // to us (to the current user) that allows us to search all content we have permission to view. - const { - data: searchEndpointData, - isLoading - } = useQuery({ - queryKey: ['content_search'], - queryFn: async () => { - const url = new URL('api/content_search/v2/studio/', getConfig().STUDIO_BASE_URL).href; - const response = await getAuthenticatedHttpClient().get(url); - return { - url: response.data.url as string, - indexName: response.data.index_name as string, - apiKey: response.data.api_key as string, - }; - }, - staleTime: 60 * 60, // If cache is up to one hour old, no need to re-fetch - refetchOnWindowFocus: false, // This doesn't need to be refreshed when the user switches back to this tab. - }); - - return ( - - - {intl.formatMessage(messages.courseSearchTitle)} - - - { - searchEndpointData ? <> - - - : (isLoading ? : <>Error) - } - - - ); -}; - -export default SearchModal; diff --git a/src/header/SearchUI.tsx b/src/header/SearchUI.tsx deleted file mode 100644 index 0a0eb4a5fc..0000000000 --- a/src/header/SearchUI.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { - HierarchicalMenu, - Highlight, - InfiniteHits, - InstantSearch, - RefinementList, - SearchBox, - Stats, -} from "react-instantsearch-dom"; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { instantMeiliSearch } from "@meilisearch/instant-meilisearch"; -import "instantsearch.css/themes/algolia-min.css"; - -interface Props { - url: string; - apiKey: string; - indexName: string; -} - - -const SearchUI: React.FC = (props) => { - const intl = useIntl(); - - const {searchClient} = React.useMemo(() => instantMeiliSearch(props.url, props.apiKey), [props.url, props.apiKey]); - - return ( -
- - - - Refine by component type: - - Refine by tag: - - - -
- ); -}; - -const Hit = ({ hit }) => ( -
-
- -
-

-
- - -
-
{hit.breadcrumbs.map((bc, i) => {bc.display_name} {i !== hit.breadcrumbs.length - 1 ? '>' : ''} )}
-
-); - -export default SearchUI; diff --git a/src/search-modal/SearchModal.jsx b/src/search-modal/SearchModal.jsx new file mode 100644 index 0000000000..0e6e81abbe --- /dev/null +++ b/src/search-modal/SearchModal.jsx @@ -0,0 +1,75 @@ +/* eslint-disable react/prop-types */ +// @ts-check +import React from 'react'; +import { + ModalDialog, +} from '@openedx/paragon'; +import { ErrorAlert } from '@edx/frontend-lib-content-components'; +import { getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { useQuery } from '@tanstack/react-query'; + +import messages from './messages'; +import { LoadingSpinner } from '../generic/Loading'; +import SearchUI from './SearchUI'; + +// Using TypeScript here is blocked until we have frontend-build 14: +// interface Props { +// courseId: string; +// isOpen: boolean; +// onClose: () => void; +// } + +/** @type {React.FC<{courseId: string, isOpen: boolean, onClose: () => void}>} */ +const SearchModal = ({ courseId, ...props }) => { + const intl = useIntl(); + + // Load the Meilisearch connection details from the LMS: the URL to use, the index name, and an API key specific + // to us (to the current user) that allows us to search all content we have permission to view. + const { + data: searchEndpointData, + isLoading, + error, + } = useQuery({ + queryKey: ['content_search'], + queryFn: async () => { + const url = new URL('api/content_search/v2/studio/', getConfig().STUDIO_BASE_URL).href; + const response = await getAuthenticatedHttpClient().get(url); + return { + url: response.data.url, + indexName: response.data.index_name, + apiKey: response.data.api_key, + }; + }, + staleTime: 60 * 60, // If cache is up to one hour old, no need to re-fetch + refetchOnWindowFocus: false, // This doesn't need to be refreshed when the user switches back to this tab. + }); + + const title = intl.formatMessage(messages['courseSearch.title']); + let body; + if (searchEndpointData) { + body = ; + } else if (isLoading) { + body = ; + } else { + // @ts-ignore + body = {error?.message ?? String(error)}; + } + + return ( + + {title} + {body} + + ); +}; + +export default SearchModal; diff --git a/src/search-modal/SearchResult.jsx b/src/search-modal/SearchResult.jsx new file mode 100644 index 0000000000..3fe40dbc05 --- /dev/null +++ b/src/search-modal/SearchResult.jsx @@ -0,0 +1,32 @@ +/* eslint-disable react/prop-types */ +// @ts-check +import React from 'react'; +import { + Highlight, + +} from 'react-instantsearch-dom'; + +/** @type {React.FC<{hit: import('react-instantsearch-core').Hit<{ + * id: string, + * breadcrumbs: {display_name: string}[]}>, + * }>} */ +const SearchResult = ({ hit }) => ( +
+
+ +
+

+
+ + +
+
+ {hit.breadcrumbs.map((bc, i) => ( + // eslint-disable-next-line react/no-array-index-key + {bc.display_name} {i !== hit.breadcrumbs.length - 1 ? '>' : ''} + ))} +
+
+); + +export default SearchResult; diff --git a/src/search-modal/SearchUI.jsx b/src/search-modal/SearchUI.jsx new file mode 100644 index 0000000000..f276c9a7c5 --- /dev/null +++ b/src/search-modal/SearchUI.jsx @@ -0,0 +1,44 @@ +/* eslint-disable react/prop-types */ +// @ts-check +import React from 'react'; +import { + HierarchicalMenu, + InfiniteHits, + InstantSearch, + RefinementList, + SearchBox, + Stats, +} from 'react-instantsearch-dom'; +import { instantMeiliSearch } from '@meilisearch/instant-meilisearch'; +import 'instantsearch.css/themes/algolia-min.css'; + +import SearchResult from './SearchResult'; + +/** @type {React.FC<{url: string, apiKey: string, indexName: string}>} */ +const SearchUI = (props) => { + const { searchClient } = React.useMemo(() => instantMeiliSearch(props.url, props.apiKey), [props.url, props.apiKey]); + + return ( +
+ + + + Refine by component type: + + Refine by tag: + + + +
+ ); +}; + +export default SearchUI; diff --git a/src/search-modal/messages.js b/src/search-modal/messages.js new file mode 100644 index 0000000000..cbca9409f8 --- /dev/null +++ b/src/search-modal/messages.js @@ -0,0 +1,15 @@ +// @ts-check +import { defineMessages as _defineMessages } from '@edx/frontend-platform/i18n'; + +// frontend-platform currently doesn't provide types... do it ourselves. +const defineMessages = /** @type {(x: T) => x} */(_defineMessages); + +const messages = defineMessages({ + 'courseSearch.title': { + id: 'courseSearch.title', + defaultMessage: 'Search Course(s)', + description: 'Title for the course search dialog', + }, +}); + +export default messages; From f454ec8eb017d8e3f6ab037686859f9f6bcd9e92 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 22 Mar 2024 14:48:47 -0700 Subject: [PATCH 6/6] chore: change modal dialog title to match design --- src/search-modal/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search-modal/messages.js b/src/search-modal/messages.js index cbca9409f8..b0dc782732 100644 --- a/src/search-modal/messages.js +++ b/src/search-modal/messages.js @@ -7,7 +7,7 @@ const defineMessages = /** @type {(x: T) => x} */(_defineMessages); const messages = defineMessages({ 'courseSearch.title': { id: 'courseSearch.title', - defaultMessage: 'Search Course(s)', + defaultMessage: 'Search', description: 'Title for the course search dialog', }, });