diff --git a/website/components/search-bar/index.jsx b/website/components/search-bar/index.jsx index af90c4b6f..235f04641 100644 --- a/website/components/search-bar/index.jsx +++ b/website/components/search-bar/index.jsx @@ -13,7 +13,16 @@ export default function SearchBar() { )} + resolveHitLink={(hit) => ({ + href: { + pathname: `/${transformIdtoUrl(hit.objectID)}`, + }, + })} placeholder="Search Nomad documentation" /> ) } + +function transformIdtoUrl(id) { + return id.replace(/\/index$/, '') +} diff --git a/website/components/search-bar/style.css b/website/components/search-bar/style.css index c69e03983..2e96e97a4 100644 --- a/website/components/search-bar/style.css +++ b/website/components/search-bar/style.css @@ -1,6 +1,6 @@ .g-search { + width: calc(100% - 2rem); max-width: 600px; - margin-left: 0 !important; & .c-hits .ais-Highlight-highlighted { background-color: #c1f1e0; diff --git a/website/package-lock.json b/website/package-lock.json index 50cf4ff53..181ad4e32 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1731,14 +1731,20 @@ } }, "@hashicorp/react-search": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@hashicorp/react-search/-/react-search-1.0.0.tgz", - "integrity": "sha512-qkeQJPVskifQuVk1Dw4Jf6VQGBV7tYqL92CdEh46CP2rsxe0yxEr5tx9pk48UytMRIr6DmdX7l0d7kfPgpACkA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@hashicorp/react-search/-/react-search-2.1.0.tgz", + "integrity": "sha512-vaTht+2G9ipsVyusK3b3TtUpuy9ccsxj3NMSWXJyGsoT39K1Oovb8aLiIlbUU5Ll72KEi5yq5OS3WAJDdSqW+g==", "requires": { "@hashicorp/react-inline-svg": "^1.0.2", "@hashicorp/remark-plugins": "^3.0.0", + "algoliasearch": "^4.4.0", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "gray-matter": "^4.0.2", "react-instantsearch-dom": "^6.7.0", - "search-insights": "^1.6.0" + "remark": "^12.0.1", + "search-insights": "^1.6.0", + "unist-util-visit": "^2.0.3" }, "dependencies": { "@hashicorp/react-inline-svg": { @@ -13728,9 +13734,9 @@ } }, "search-insights": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-1.6.0.tgz", - "integrity": "sha512-JBbu9WDL72gSgjFTaCeHntsXiOk+E0N1h6WLlWlWnjb1p/fLzza1wFx4hh1t1DrweEGfrU29+3J5ORkcHMI7Kg==" + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-1.6.2.tgz", + "integrity": "sha512-mpy+57HZVMZH5HsMHYMCLvkf+tUvhy+ycP2tDy1j7wmj+mQsNZ3LC61IcMYomok02NozaMR3GiGyfH6uc+ibdA==" }, "section-matter": { "version": "1.0.0", diff --git a/website/package.json b/website/package.json index 8cb7c4a07..5b29c284b 100644 --- a/website/package.json +++ b/website/package.json @@ -19,7 +19,7 @@ "@hashicorp/react-inline-svg": "1.0.0", "@hashicorp/react-mega-nav": "4.0.1-2", "@hashicorp/react-product-downloader": "4.0.2", - "@hashicorp/react-search": "^1.0.0", + "@hashicorp/react-search": "^2.1.0", "@hashicorp/react-section-header": "2.0.0", "@hashicorp/react-subnav": "3.2.3", "@hashicorp/react-text-and-content": "4.1.1", @@ -28,11 +28,8 @@ "@hashicorp/react-text-split-with-image": "1.3.1", "@hashicorp/react-use-cases": "1.0.4", "@hashicorp/react-vertical-text-block-list": "2.0.1", - "algoliasearch": "^4.4.0", "babel-plugin-import-glob-array": "0.2.0", "dotenv": "^8.2.0", - "glob": "^7.1.6", - "gray-matter": "^4.0.2", "imagemin-mozjpeg": "9.0.0", "imagemin-optipng": "8.0.0", "imagemin-svgo": "8.0.0", @@ -41,9 +38,7 @@ "nuka-carousel": "4.7.0", "react": "16.13.1", "react-device-detect": "1.13.1", - "react-dom": "16.13.1", - "remark": "^12.0.0", - "unist-util-visit": "^2.0.3" + "react-dom": "16.13.1" }, "devDependencies": { "dart-linkcheck": "2.0.15", diff --git a/website/scripts/index_search_content.js b/website/scripts/index_search_content.js index 7eeb7524f..f853b4ddf 100644 --- a/website/scripts/index_search_content.js +++ b/website/scripts/index_search_content.js @@ -1,125 +1,3 @@ -require('dotenv').config() +const { indexDocsContent } = require('@hashicorp/react-search/tools') -const algoliasearch = require('algoliasearch') -const glob = require('glob') -const matter = require('gray-matter') -const path = require('path') -const remark = require('remark') -const visit = require('unist-util-visit') - -// In addition to the content of the page, -// define additional front matter attributes that will be search-indexable -const SEARCH_DIMENSIONS = ['page_title', 'description'] - -main() - -async function main() { - const pagesFolder = path.join(__dirname, '../pages') - - // Grab all search-indexable content and format for Algolia - const searchObjects = await Promise.all( - glob.sync(path.join(pagesFolder, '**/*.mdx')).map(async (fullPath) => { - const { content, data } = matter.read(fullPath) - - const searchableDimensions = SEARCH_DIMENSIONS.reduce( - (acc, dimension) => { - return { ...acc, [dimension]: data[dimension] } - }, - {} - ) - - const headings = await collectHeadings(content) - - // Get path relative to `pages` - const __resourcePath = fullPath.replace(`${pagesFolder}/`, '') - - // Use clean URL for Algolia id - const objectID = __resourcePath.replace('.mdx', '') - - return { - ...searchableDimensions, - headings, - objectID, - } - }) - ) - - try { - await indexSearchContent(searchObjects) - } catch (e) { - console.error(e) - process.exit(1) - } -} - -async function indexSearchContent(objects) { - const { - NEXT_PUBLIC_ALGOLIA_APP_ID: appId, - NEXT_PUBLIC_ALGOLIA_INDEX: index, - ALGOLIA_API_KEY: apiKey, - } = process.env - - if (!apiKey || !appId || !index) { - throw new Error( - `[*** Algolia Search Indexing Error ***] Received: ALGOLIA_API_KEY=${apiKey} ALGOLIA_APP_ID=${appId} ALGOLIA_INDEX=${index} \n Please ensure all Algolia Search-related environment vars are set in CI settings.` - ) - } - - console.log(`updating ${objects.length} indices...`) - - try { - const searchClient = algoliasearch(appId, apiKey) - const searchIndex = searchClient.initIndex(index) - - const { objectIDs } = await searchIndex.partialUpdateObjects(objects, { - createIfNotExists: true, - }) - - let staleIds = [] - - await searchIndex.browseObjects({ - query: '', - batch: (batch) => { - staleIds = staleIds.concat( - batch - .filter(({ objectID }) => !objectIDs.includes(objectID)) - .map(({ objectID }) => objectID) - ) - }, - }) - - if (staleIds.length > 0) { - console.log(`deleting ${staleIds.length} stale indices:`) - console.log(staleIds) - - await searchIndex.deleteObjects(staleIds) - } - - console.log('done') - process.exit(0) - } catch (error) { - throw new Error(error) - } -} - -async function collectHeadings(mdxContent) { - const headings = [] - - const headingMapper = () => (tree) => { - visit(tree, 'heading', (node) => { - const title = node.children.reduce((m, n) => { - if (n.value) m += n.value - return m - }, '') - // Only include level 1 or level 2 headings - if (node.depth < 3) { - headings.push(title) - } - }) - } - - return remark() - .use(headingMapper) - .process(mdxContent) - .then(() => headings) -} +indexDocsContent()