Skip to content

Commit

Permalink
website: Add Algolia Docsearch (#2546)
Browse files Browse the repository at this point in the history
* Initial spike

* Cleanup

* manypkg fix

* Remove unused vars

* hardcode keys

* Add docsearch

* Remove old search

* Remove dotenv

* Remove comment

* Sidebar cleanup

* Fix navigate

* Remove getHash
  • Loading branch information
jordanoverbye authored Mar 20, 2020
1 parent abac6ad commit 1b188f7
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 324 deletions.
2 changes: 1 addition & 1 deletion demo-projects/blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"apollo-upload-client": "^12.1.0",
"cross-env": "^7.0.0",
"date-fns": "^1.30.1",
"dotenv": "^8.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"graphql-tag": "^2.10.1",
"isomorphic-unfetch": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion demo-projects/meetup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"body-parser": "^1.18.2",
"cross-env": "^7.0.0",
"date-fns": "^1.30.1",
"dotenv": "^8.0.0",
"dotenv": "^8.2.0",
"eslint-plugin-emotion": "^10.0.27",
"express": "^4.17.1",
"facepaint": "^1.2.1",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"cross-env": "^7.0.0",
"cypress": "^3.5.0",
"cypress-multi-reporters": "^1.2.3",
"dotenv": "^8.0.0",
"dotenv": "^8.2.0",
"dotenv-safe": "^8.1.0",
"endent": "^1.3.0",
"eslint": "^6.8.0",
Expand Down
44 changes: 0 additions & 44 deletions website/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,50 +103,6 @@ async function getGatsbyConfig() {
rehypePlugins: [[require('@mapbox/rehype-prism'), { ignoreMissing: true }]],
},
},
{
resolve: 'gatsby-plugin-lunr',
options: {
languages: [
{
name: 'en',
filterNodes: node => {
const { context } = node;
const { fields } = node;
// I'm not sure why... but we get different types of nodes here
if (context || fields) {
// Only only return false if draft is set to true,
// undefined should default to not draft
const draft = (context && context.draft) || (fields && fields.draft) || false;
return Boolean(!draft);
}
return true;
},
},
],
// Fields to index. If store === true value will be stored in index file.
// Attributes for custom indexing logic. See https://lunrjs.com/docs/lunr.Builder.html for details
fields: [
{ name: 'content' },
{ name: 'navGroup', store: true },
{ name: 'navSubGroup', store: true },
{ name: 'slug', store: true },
{ name: 'title', store: true, attributes: { boost: 20 } },
],
// How to resolve each field's value for a supported node type
resolvers: {
// For any node of type mdx, list how to resolve the fields' values
Mdx: {
content: node => node.rawBody,
navGroup: node => node.fields.navGroup,
navSubGroup: node => node.fields.navSubGroup,
slug: node => node.fields.slug,
title: node => node.fields.pageTitle,
},
},
//custom index file name, default is search_index.json
filename: 'search_index.json',
},
},
{
resolve: `gatsby-plugin-google-analytics`,
options: {
Expand Down
1 change: 0 additions & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"facepaint": "^1.2.1",
"gatsby": "^2.13.25",
"gatsby-plugin-google-analytics": "^2.1.4",
"gatsby-plugin-lunr": "^1.4.0",
"gatsby-plugin-manifest": "^2.2.41",
"gatsby-plugin-mdx": "^1.0.64",
"gatsby-plugin-react-helmet": "^3.1.2",
Expand Down
1 change: 1 addition & 0 deletions website/src/components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const Footer = () => (
paddingBottom: '1.25em',
paddingTop: '1.25em',
textAlign: 'center',
marginBottom: '2rem',
}}
>
Made with ❤️ by{' '}
Expand Down
47 changes: 10 additions & 37 deletions website/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { jsx } from '@emotion/core';
import { colors, gridSize } from '@arch-ui/theme';

import logosvg from '../assets/logo.svg';
import { Container, SocialIconsNav } from '../components';
import { Container, SocialIconsNav, Search } from '../components';
import { media, mediaOnly, mediaMax } from '../utils/media';

export const HEADER_HEIGHT = 60;
Expand All @@ -18,14 +18,19 @@ export const Header = forwardRef(({ toggleMenu, ...props }, ref) => (
css={{
alignItems: 'center',
boxShadow: `0 1px 0 ${colors.N10}`,
display: 'flex',
fontSize: '0.9rem',
display: 'grid',
gridTemplateColumns: '150px 1fr auto',
gridGap: '1rem',
fontWeight: 500,
height: HEADER_HEIGHT,
justifyContent: 'space-between',

[media.sm]: {
gridTemplateColumns: '220px 1fr 220px',
},
}}
>
<Logo />
<Search />
<Nav toggleMenu={toggleMenu} />
</div>
</Container>
Expand All @@ -44,6 +49,7 @@ const Logo = () => (
alignItems: 'center',
color: 'inherit',
display: 'inline-flex',
fontSize: '0.9rem',

':hover': {
textDecoration: 'none',
Expand Down Expand Up @@ -127,15 +133,9 @@ const List = props => (
const Nav = ({ toggleMenu }) => (
<nav>
<List>
{NAV_LINKS.map(({ url, name }) => (
<NavItem key={name} to={url} lgOnly>
{name}
</NavItem>
))}
<li>
<SocialIconsNav
css={{
marginLeft: '2rem',
[mediaMax.sm]: {
display: 'none',
},
Expand All @@ -162,30 +162,3 @@ const Nav = ({ toggleMenu }) => (
</List>
</nav>
);

// ==============================
// Data
// ==============================

const NAV_LINKS = [
{
name: 'Quick Start',
url: '/quick-start/',
},
{
name: 'Tutorials',
url: '/tutorials/',
},
{
name: 'Guides',
url: '/guides/',
},
{
name: 'API',
url: '/api/',
},
{
name: 'Packages',
url: '/packages/',
},
];
181 changes: 33 additions & 148 deletions website/src/components/Search.js
Original file line number Diff line number Diff line change
@@ -1,165 +1,50 @@
/** @jsx jsx */

import { useState, useEffect, useCallback } from 'react';
import debounce from 'lodash.debounce';
import { jsx } from '@emotion/core';
import Select from '@arch-ui/select';
import { useEffect, useRef } from 'react';
import { navigate } from 'gatsby';
import { Input } from '@arch-ui/input';

import { getResults } from '../utils/search';
import { addCallback } from '../utils/async-load-search';
import { algoliaStyles } from '../utils/algolia-styles';

const buildOptions = arr => {
let ops = [];

arr.forEach(i => {
if (!i.navGroup) {
return;
}

const groupLabel = i.navGroup.replace('-', ' ');

if (!ops.some(o => o.label === groupLabel)) {
ops.push({
label: groupLabel,
options: [],
});
}

ops.find(o => o.label === groupLabel).options.push(i);
});

return ops;
};
const searchId = 'algolia-doc-search';

export const Search = () => {
let [query, setQuery] = useState('');
let [results, setResults] = useState([]);

const setQueryDebounced = useCallback(
debounce(value => setQuery(value), 200),
[setQuery]
);
const inputRef = useRef();

useEffect(() => {
let cancelled = false;

if (!query) {
return;
}

getResults(query, { limit: 20 }).then(queryResults => {
if (cancelled) {
return;
addCallback(loaded => {
if (loaded) {
window.docsearch({
apiKey: '211e94c001e6b4c6744ae72fb252eaba',
indexName: 'keystonejs',
inputSelector: `#${searchId}`,
handleSelected: (input, e, suggestion) => {
e.preventDefault();
input.setVal('');
input.close();
inputRef.current.blur();
const url = suggestion.url.replace(/https*:\/\/[www.]*keystonejs\.com/, '');
navigate(url);
},
});
} else {
// eslint-disable-next-line no-console
console.warn('Search has failed to load and is now disabled');
}

setResults(buildOptions(queryResults.results));
});

return () => {
cancelled = true;
};
}, [query]);
}, []);

return (
<Select
key="select"
components={{ Control, DropdownIndicator, IndicatorSeparator, Input }}
placeholder="Search..."
options={results}
value={null}
onInputChange={setQueryDebounced}
openMenuOnClick={false}
tabSelectsValue={false}
onChange={result => {
setQueryDebounced.cancel();
navigate(result.slug);
setQuery('');
}}
css={{ zIndex: 2 }}
filterOption={filterOption}
getOptionValue={result => result.slug}
getOptionLabel={result => result.title}
noOptionsMessage={() => (query ? 'No results found' : 'Enter a search term')}
/>
);
};

// ==============================
// Styled Components
// ==============================

const DropdownIndicator = ({ innerProps, isFocused }) => (
<div css={{ padding: '4px 6px', opacity: isFocused ? 0.8 : 0.6 }} {...innerProps}>
<svg width="24" height="24" viewBox="0 0 24 24" focusable="false" role="presentation">
<path
d="M14.823 15.883a5.5 5.5 0 1 1 1.06-1.06l2.647 2.647c.293.293.53.59 0 1.06-.53.47-.767.293-1.06 0l-2.647-2.647zM11.5 15.5a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"
fill="currentColor"
<form css={[algoliaStyles]}>
<Input
ref={inputRef}
id={searchId}
type="search"
placeholder="Search..."
aria-label="Search..."
/>
</svg>
</div>
);
const Control = ({ children, innerProps, innerRef, isFocused }) => {
const backgroundColor = isFocused ? 'rgba(9, 30, 66, 0.1)' : 'rgba(9, 30, 66, 0.05)';
return (
<div
ref={innerRef}
css={{
backgroundColor,
boxSizing: 'border-box',
borderRadius: 4,
display: 'flex',
paddingLeft: 2,
paddingRight: 2,
}}
{...innerProps}
>
{children}
</div>
</form>
);
};
const Input = ({
className,
cx,
getStyles,
innerRef,
isDisabled,
isHidden,
selectProps,
theme,
...props
}) => (
<div
css={{
margin: 2,
paddingBottom: 2,
paddingTop: 2,
visibility: isDisabled ? 'hidden' : 'visible',
}}
>
<input
ref={innerRef}
css={{
background: 0,
border: 0,
color: 'inherit',
fontSize: 'inherit',
opacity: isHidden ? 0 : 1,
outline: 0,
padding: 0,

'&.focus-visible': {
outline: 0,
},
}}
{...props}
/>
</div>
);
const IndicatorSeparator = null;

// ==============================
// Utilities
// ==============================

// remove options that aren't present in the sidebar
let filterOption = ({ data }) => Boolean(data.navGroup);
Loading

0 comments on commit 1b188f7

Please sign in to comment.