Integrate @hashicorp/react-search into layout

This commit is contained in:
Kevin Pruett 2020-09-01 11:59:08 -04:00
parent a1fe711390
commit b94d24fcd0
9 changed files with 165 additions and 166 deletions

View File

@ -0,0 +1,28 @@
import Search from '@hashicorp/react-search'
export default function SearchBar() {
return (
<Search
renderHitContent={({ hit, Highlight }) => (
<>
<span className="name">
<Highlight attribute="page_title" hit={hit} tagName="span" />
</span>
<span className="description">
<Highlight attribute="description" hit={hit} tagName="span" />
</span>
</>
)}
resolveHitLink={(hit) => ({
href: {
pathname: `/${transformIdtoUrl(hit.objectID)}`,
},
})}
placeholder="Search Consul documentation"
/>
)
}
function transformIdtoUrl(id) {
return id.replace(/\/index$/, '')
}

View File

@ -0,0 +1,4 @@
.g-search {
width: calc(100% - 2rem);
max-width: 600px;
}

View File

@ -1,18 +1,21 @@
import DocsPage from '@hashicorp/react-docs-page'
import order from '../data/api-navigation.js'
import { frontMatter as data } from '../pages/api-docs/**/*.mdx'
import Head from 'next/head' import Head from 'next/head'
import Link from 'next/link' import Link from 'next/link'
import { createMdxProvider } from '@hashicorp/nextjs-scripts/lib/providers/docs' import { createMdxProvider } from '@hashicorp/nextjs-scripts/lib/providers/docs'
import DocsPage from '@hashicorp/react-docs-page'
import { SearchProvider } from '@hashicorp/react-search'
import SearchBar from '../components/search-bar'
import { frontMatter as data } from '../pages/api-docs/**/*.mdx'
import order from '../data/api-navigation.js'
const MDXProvider = createMdxProvider({ product: 'consul' }) const MDXProvider = createMdxProvider({ product: 'consul' })
function ApiDocsLayoutWrapper(pageMeta) { function ApiDocsLayoutWrapper(pageMeta) {
function ApiDocsLayout(props) { function ApiDocsLayout(props) {
const { children, ...propsWithoutChildren } = props
return ( return (
<MDXProvider> <MDXProvider>
<DocsPage <DocsPage
{...props} {...propsWithoutChildren}
product="consul" product="consul"
head={{ head={{
is: Head, is: Head,
@ -25,10 +28,16 @@ function ApiDocsLayoutWrapper(pageMeta) {
category: 'api-docs', category: 'api-docs',
currentPage: props.path, currentPage: props.path,
data, data,
disableFilter: true,
order, order,
}} }}
resourceURL={`https://github.com/hashicorp/consul/blob/master/website/pages/${pageMeta.__resourcePath}`} resourceURL={`https://github.com/hashicorp/consul/blob/master/website/pages/${pageMeta.__resourcePath}`}
/> >
<SearchProvider>
<SearchBar />
{children}
</SearchProvider>
</DocsPage>
</MDXProvider> </MDXProvider>
) )
} }

View File

@ -1,18 +1,21 @@
import DocsPage from '@hashicorp/react-docs-page'
import order from '../data/docs-navigation.js'
import { frontMatter as data } from '../pages/docs/**/*.mdx'
import Head from 'next/head' import Head from 'next/head'
import Link from 'next/link' import Link from 'next/link'
import { createMdxProvider } from '@hashicorp/nextjs-scripts/lib/providers/docs' import { createMdxProvider } from '@hashicorp/nextjs-scripts/lib/providers/docs'
import DocsPage from '@hashicorp/react-docs-page'
import { SearchProvider } from '@hashicorp/react-search'
import SearchBar from '../components/search-bar'
import { frontMatter as data } from '../pages/docs/**/*.mdx'
import order from '../data/docs-navigation.js'
const MDXProvider = createMdxProvider({ product: 'consul' }) const MDXProvider = createMdxProvider({ product: 'consul' })
function DocsLayoutWrapper(pageMeta) { function DocsLayoutWrapper(pageMeta) {
function DocsLayout(props) { function DocsLayout(props) {
const { children, ...propsWithoutChildren } = props
return ( return (
<MDXProvider> <MDXProvider>
<DocsPage <DocsPage
{...props} {...propsWithoutChildren}
product="consul" product="consul"
head={{ head={{
is: Head, is: Head,
@ -25,10 +28,16 @@ function DocsLayoutWrapper(pageMeta) {
category: 'docs', category: 'docs',
currentPage: props.path, currentPage: props.path,
data, data,
disableFilter: true,
order, order,
}} }}
resourceURL={`https://github.com/hashicorp/consul/blob/master/website/pages/${pageMeta.__resourcePath}`} resourceURL={`https://github.com/hashicorp/consul/blob/master/website/pages/${pageMeta.__resourcePath}`}
/> >
<SearchProvider>
<SearchBar />
{children}
</SearchProvider>
</DocsPage>
</MDXProvider> </MDXProvider>
) )
} }

View File

@ -1,18 +1,21 @@
import DocsPage from '@hashicorp/react-docs-page'
import order from '../data/intro-navigation.js'
import { frontMatter as data } from '../pages/intro/**/*.mdx'
import Head from 'next/head' import Head from 'next/head'
import Link from 'next/link' import Link from 'next/link'
import { createMdxProvider } from '@hashicorp/nextjs-scripts/lib/providers/docs' import { createMdxProvider } from '@hashicorp/nextjs-scripts/lib/providers/docs'
import DocsPage from '@hashicorp/react-docs-page'
import { SearchProvider } from '@hashicorp/react-search'
import SearchBar from '../components/search-bar'
import { frontMatter as data } from '../pages/intro/**/*.mdx'
import order from '../data/intro-navigation.js'
const MDXProvider = createMdxProvider({ product: 'consul' }) const MDXProvider = createMdxProvider({ product: 'consul' })
function IntroLayoutWrapper(pageMeta) { function IntroLayoutWrapper(pageMeta) {
function IntroLayout(props) { function IntroLayout(props) {
const { children, ...propsWithoutChildren } = props
return ( return (
<MDXProvider> <MDXProvider>
<DocsPage <DocsPage
{...props} {...propsWithoutChildren}
product="consul" product="consul"
head={{ head={{
is: Head, is: Head,
@ -25,10 +28,16 @@ function IntroLayoutWrapper(pageMeta) {
category: 'intro', category: 'intro',
currentPage: props.path, currentPage: props.path,
data, data,
disableFilter: true,
order, order,
}} }}
resourceURL={`https://github.com/hashicorp/consul/blob/master/website/pages/${pageMeta.__resourcePath}`} resourceURL={`https://github.com/hashicorp/consul/blob/master/website/pages/${pageMeta.__resourcePath}`}
/> >
<SearchProvider>
<SearchBar />
{children}
</SearchProvider>
</DocsPage>
</MDXProvider> </MDXProvider>
) )
} }

View File

@ -3768,6 +3768,23 @@
"@hashicorp/react-image": "^2.0.3" "@hashicorp/react-image": "^2.0.3"
} }
}, },
"@hashicorp/react-search": {
"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",
"remark": "^12.0.1",
"search-insights": "^1.6.0",
"unist-util-visit": "^2.0.3"
}
},
"@hashicorp/react-section-header": { "@hashicorp/react-section-header": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@hashicorp/react-section-header/-/react-section-header-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@hashicorp/react-section-header/-/react-section-header-2.0.2.tgz",
@ -4584,6 +4601,21 @@
"@algolia/transporter": "4.4.0" "@algolia/transporter": "4.4.0"
} }
}, },
"algoliasearch-helper": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.2.2.tgz",
"integrity": "sha512-/3XvE33R+gQKaiPdy3nmHYqhF8hqIu8xnlOicVxb1fD6uMFmxW8rGLzzrRfsPfxgAfm+c1NslLb3TzQVIB8aVA==",
"requires": {
"events": "^1.1.1"
},
"dependencies": {
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
}
}
},
"ally.js": { "ally.js": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/ally.js/-/ally.js-1.4.1.tgz", "resolved": "https://registry.npmjs.org/ally.js/-/ally.js-1.4.1.tgz",
@ -14310,6 +14342,34 @@
} }
} }
}, },
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"react-instantsearch-core": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.7.0.tgz",
"integrity": "sha512-wIvSIwkWfqPbaQZcbKsfBK3Gpm1e7ahSwU8Bmx1N5RfUqA/NghqS0Ppv3sz4vCXjoEAdPV06R+Fpn9lT+cE9/Q==",
"requires": {
"@babel/runtime": "^7.1.2",
"algoliasearch-helper": "^3.1.0",
"prop-types": "^15.5.10",
"react-fast-compare": "^3.0.0"
}
},
"react-instantsearch-dom": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/react-instantsearch-dom/-/react-instantsearch-dom-6.7.0.tgz",
"integrity": "sha512-J1C9xkHHLLa6rkKLKFDa7szA0TDo6yPFGmDzh2+JLaq4o694RIqivfUpROHus0Ki3BAQu9QmzLtodf6K1NOBWQ==",
"requires": {
"@babel/runtime": "^7.1.2",
"algoliasearch-helper": "^3.1.0",
"classnames": "^2.2.5",
"prop-types": "^15.5.10",
"react-instantsearch-core": "^6.7.0"
}
},
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -15320,6 +15380,11 @@
"ajv-keywords": "^3.1.0" "ajv-keywords": "^3.1.0"
} }
}, },
"search-insights": {
"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": { "section-matter": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",

View File

@ -22,6 +22,7 @@
"@hashicorp/react-mega-nav": "4.0.1-2", "@hashicorp/react-mega-nav": "4.0.1-2",
"@hashicorp/react-product-downloader": "4.1.1", "@hashicorp/react-product-downloader": "4.1.1",
"@hashicorp/react-product-features-list": "1.0.3", "@hashicorp/react-product-features-list": "1.0.3",
"@hashicorp/react-search": "^2.1.0",
"@hashicorp/react-section-header": "2.0.2", "@hashicorp/react-section-header": "2.0.2",
"@hashicorp/react-subnav": "3.2.6", "@hashicorp/react-subnav": "3.2.6",
"@hashicorp/react-text-and-content": "4.1.4", "@hashicorp/react-text-and-content": "4.1.4",
@ -31,21 +32,15 @@
"@hashicorp/react-text-split-with-logo-grid": "1.3.0", "@hashicorp/react-text-split-with-logo-grid": "1.3.0",
"@hashicorp/react-use-cases": "1.0.6", "@hashicorp/react-use-cases": "1.0.6",
"@hashicorp/react-vertical-text-block-list": "2.0.3", "@hashicorp/react-vertical-text-block-list": "2.0.3",
"algoliasearch": "4.4.0",
"babel-plugin-import-glob-array": "0.2.0", "babel-plugin-import-glob-array": "0.2.0",
"dotenv": "8.2.0",
"gray-matter": "4.0.2",
"next": "9.4.4", "next": "9.4.4",
"nuka-carousel": "4.7.0", "nuka-carousel": "4.7.0",
"react": "16.13.1", "react": "16.13.1",
"react-device-detect": "1.13.1", "react-device-detect": "1.13.1",
"react-dom": "16.13.1", "react-dom": "16.13.1"
"remark": "12.0.1",
"unist-util-visit": "2.0.3"
}, },
"devDependencies": { "devDependencies": {
"dart-linkcheck": "2.0.15", "dart-linkcheck": "2.0.15",
"glob": "7.1.6",
"husky": "4.2.5", "husky": "4.2.5",
"prettier": "2.0.5" "prettier": "2.0.5"
}, },

View File

@ -10,31 +10,32 @@
--highlight-color: var(--consul); --highlight-color: var(--consul);
} }
@import '~@hashicorp/react-button/dist/style.css'; @import '~@hashicorp/react-alert-banner/dist/style.css';
@import '~@hashicorp/react-section-header/dist/style.css';
@import '~@hashicorp/react-logo-grid/dist/style.css';
@import '~@hashicorp/react-product-features-list/dist/style.css';
@import '~@hashicorp/react-product-downloader/dist/style.css';
@import '~@hashicorp/react-vertical-text-block-list/dist/style.css';
@import '~@hashicorp/react-docs-sidenav/dist/style.css';
@import '~@hashicorp/react-content/dist/style.css';
@import '~@hashicorp/react-subnav/dist/style.css';
@import '~@hashicorp/react-text-and-content/dist/style.css';
@import '~@hashicorp/react-consent-manager/dist/style.css';
@import '~@hashicorp/react-toggle/dist/style.css';
@import '~@hashicorp/react-alert/dist/style.css'; @import '~@hashicorp/react-alert/dist/style.css';
@import '~@hashicorp/react-text-split/dist/style.css'; @import '~@hashicorp/react-button/dist/style.css';
@import '~@hashicorp/react-text-split-with-code/dist/style.css';
@import '~@hashicorp/react-enterprise-alert/dist/style.css';
@import '~@hashicorp/react-mega-nav/style.css';
@import '~@hashicorp/react-docs-page/style.css';
@import '~@hashicorp/react-call-to-action/dist/style.css'; @import '~@hashicorp/react-call-to-action/dist/style.css';
@import '~@hashicorp/react-case-study-slider/dist/style.css'; @import '~@hashicorp/react-case-study-slider/dist/style.css';
@import '~@hashicorp/react-tabs/dist/style.css';
@import '~@hashicorp/react-code-block/dist/style.css'; @import '~@hashicorp/react-code-block/dist/style.css';
@import '~@hashicorp/react-alert-banner/dist/style.css'; @import '~@hashicorp/react-consent-manager/dist/style.css';
@import '~@hashicorp/react-use-cases/dist/style.css'; @import '~@hashicorp/react-content/dist/style.css';
@import '~@hashicorp/react-docs-page/style.css';
@import '~@hashicorp/react-docs-sidenav/dist/style.css';
@import '~@hashicorp/react-enterprise-alert/dist/style.css';
@import '~@hashicorp/react-featured-slider/dist/style.css'; @import '~@hashicorp/react-featured-slider/dist/style.css';
@import '~@hashicorp/react-logo-grid/dist/style.css';
@import '~@hashicorp/react-mega-nav/style.css';
@import '~@hashicorp/react-product-downloader/dist/style.css';
@import '~@hashicorp/react-product-features-list/dist/style.css';
@import '~@hashicorp/react-search/dist/style.css';
@import '~@hashicorp/react-section-header/dist/style.css';
@import '~@hashicorp/react-subnav/dist/style.css';
@import '~@hashicorp/react-tabs/dist/style.css';
@import '~@hashicorp/react-text-and-content/dist/style.css';
@import '~@hashicorp/react-text-split-with-code/dist/style.css';
@import '~@hashicorp/react-text-split/dist/style.css';
@import '~@hashicorp/react-toggle/dist/style.css';
@import '~@hashicorp/react-use-cases/dist/style.css';
@import '~@hashicorp/react-vertical-text-block-list/dist/style.css';
/* Local Components */ /* Local Components */
@import '../components/basic-hero/style.css'; @import '../components/basic-hero/style.css';
@ -43,6 +44,7 @@
@import '../components/learn-callout/style.css'; @import '../components/learn-callout/style.css';
@import '../components/case-study-carousel/style.css'; @import '../components/case-study-carousel/style.css';
@import '../components/cloud-offerings-list/style.css'; @import '../components/cloud-offerings-list/style.css';
@import '../components/search-bar/style.css';
/* Layouts */ /* Layouts */
@import '../layouts/use-cases/style.css'; @import '../layouts/use-cases/style.css';

View File

@ -1,125 +1,3 @@
require('dotenv').config() const { indexDocsContent } = require('@hashicorp/react-search/tools')
const algoliasearch = require('algoliasearch') indexDocsContent()
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)
}