💫 Add Algolia DocSearch (#3332)

* Add Algolia DocSearch

* Add human-readable selector for teaser
This commit is contained in:
Ines Montani 2019-02-25 20:11:11 +01:00 committed by GitHub
parent f2fae1f186
commit 162bd4d75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 264 additions and 45 deletions

View File

@ -22,6 +22,10 @@
"id": "83b0498b1e7fa3c91ce68c3f1",
"list": "89ad33e698"
},
"docSearch": {
"apiKey": "371e26ed49d29a27bd36273dfdaf89af",
"indexName": "spacy"
},
"spacyVersion": "2.1",
"binderUrl": "ines/spacy-io-binder",
"binderBranch": "nightly",

View File

@ -18,6 +18,7 @@ import { ReactComponent as YesIcon } from '../images/icons/yes.svg'
import { ReactComponent as NoIcon } from '../images/icons/no.svg'
import { ReactComponent as NeutralIcon } from '../images/icons/neutral.svg'
import { ReactComponent as OfflineIcon } from '../images/icons/offline.svg'
import { ReactComponent as SearchIcon } from '../images/icons/search.svg'
import classes from '../styles/icon.module.sass'
@ -39,6 +40,7 @@ const icons = {
no: NoIcon,
neutral: NeutralIcon,
offline: OfflineIcon,
search: SearchIcon,
}
const Icon = ({ name, width, height, inline, variant, className }) => {

View File

@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { navigate } from 'gatsby'
import Link from './link'
import Icon from './icon'
@ -8,36 +9,64 @@ import { github } from './util'
import { ReactComponent as Logo } from '../images/logo.svg'
import classes from '../styles/navigation.module.sass'
const Navigation = ({ title, items, section, children }) => (
<nav className={classes.root}>
<Link to="/" aria-label={title} hidden>
<h1 className={classes.title}>{title}</h1>
<Logo className={classes.logo} width={300} height={96} />
</Link>
const Dropdown = ({ items, section }) => {
const active = items.find(({ text }) => text.toLowerCase() === section)
const defaultValue = active ? active.url : 'title'
return (
<select
defaultValue={defaultValue}
className={classes.dropdown}
onChange={({ target }) => navigate(target.value)}
>
<option value="title" disabled>
Menu
</option>
{items.map(({ text, url }, i) => (
<option key={i} value={url}>
{text}
</option>
))}
</select>
)
}
<ul className={classes.menu}>
{items.map(({ text, url }, i) => {
const isActive = section && text.toLowerCase() === section
const itemClassNames = classNames(classes.item, {
[classes.isActive]: isActive,
})
return (
<li key={i} className={itemClassNames}>
<Link to={url} tabIndex={isActive ? '-1' : null} hidden>
{text}
const Navigation = ({ title, items, section, search, children }) => {
return (
<nav className={classes.root}>
<Link to="/" aria-label={title} hidden>
<h1 className={classes.title}>{title}</h1>
<Logo className={classes.logo} width={300} height={96} />
</Link>
<div className={classes.menu}>
<Dropdown items={items} section={section} />
<ul className={classes.list}>
{items.map(({ text, url }, i) => {
const isActive = section && text.toLowerCase() === section
const itemClassNames = classNames(classes.item, {
[classes.isActive]: isActive,
})
return (
<li key={i} className={itemClassNames}>
<Link to={url} tabIndex={isActive ? '-1' : null} hidden>
{text}
</Link>
</li>
)
})}
<li className={classes.item}>
<Link to={github()} aria-label="GitHub" hidden>
<Icon name="github" />
</Link>
</li>
)
})}
<li className={classes.item}>
<Link to={github()} aria-label="GitHub" hidden>
<Icon name="github" />
</Link>
</li>
</ul>
{children}
</nav>
)
</ul>
{search && <div className={classes.search}>{search}</div>}
</div>
{children}
</nav>
)
}
Navigation.defaultProps = {
items: [],
@ -52,6 +81,7 @@ Navigation.propTypes = {
})
),
section: PropTypes.string,
search: PropTypes.node,
}
export default Navigation

View File

@ -0,0 +1,52 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { window } from 'browser-monads'
import Icon from './icon'
import classes from '../styles/search.module.sass'
const Search = ({ id, placeholder, settings }) => {
const { apiKey, indexName } = settings
const [isInitialized, setIsInitialized] = useState(false)
useEffect(() => {
if (!isInitialized) {
setIsInitialized(true)
window.docsearch({
apiKey,
indexName,
inputSelector: `#${id}`,
debug: false,
})
}
}, window.docsearch)
return (
<form className={classes.root}>
<label htmlFor={id} className={classes.icon}>
<Icon name="search" width={24} />
</label>
<input
id={id}
className={classes.input}
type="search"
placeholder={placeholder}
aria-label={placeholder}
/>
</form>
)
}
Search.defaultProps = {
id: 'docsearch',
placeholder: 'Search docs',
}
Search.propTypes = {
settings: PropTypes.shape({
apiKey: PropTypes.string.isRequired,
indexName: PropTypes.string.isRequired,
}).isRequired,
id: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
}
export default Search

View File

@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import Button from './button'
import Tag from './tag'
@ -34,7 +35,7 @@ const Title = ({ title, tag, version, teaser, source, image, children, ...props
</Tag>
)}
{teaser && <div className={classes.teaser}>{teaser}</div>}
{teaser && <div className={classNames('heading-teaser', classes.teaser)}>{teaser}</div>}
{children}
</header>

43
website/src/html.js Normal file
View File

@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
export default function HTML(props) {
return (
<html {...props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{props.headComponents}
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
/>
</head>
<body {...props.bodyAttributes}>
{props.preBodyComponents}
<noscript key="noscript" id="gatsby-noscript">
This app works best with JavaScript enabled.
</noscript>
<div key={`body`} id="___gatsby" dangerouslySetInnerHTML={{ __html: props.body }} />
{props.postBodyComponents}
</body>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
/>
</html>
)
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M9.516 14.016c2.484 0 4.5-2.016 4.5-4.5s-2.016-4.5-4.5-4.5-4.5 2.016-4.5 4.5 2.016 4.5 4.5 4.5zM15.516 14.016l4.969 4.969-1.5 1.5-4.969-4.969v-0.797l-0.281-0.281c-1.125 0.984-2.625 1.547-4.219 1.547-3.609 0-6.516-2.859-6.516-6.469s2.906-6.516 6.516-6.516 6.469 2.906 6.469 6.516c0 1.594-0.563 3.094-1.547 4.219l0.281 0.281h0.797z"></path>
</svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@ -18,6 +18,7 @@
box-shadow: var(--box-shadow)
.logo
min-width: 95px
width: 95px
height: 30px
color: var(--color-theme) !important
@ -26,23 +27,12 @@
.title
display: none
.menu
.list
display: flex
height: 100%
flex-flow: row nowrap
border-color: inherit
overflow-x: auto
overflow-y: hidden
-webkit-overflow-scrolling: touch
@include breakpoint(max, sm)
.menu
@include scroll-shadow-base(var(--color-shadow))
margin-left: 1rem
@include breakpoint(min, md)
.menu
justify-content: flex-end
justify-content: flex-end
margin-bottom: 0.25rem
.item
display: inline-flex
@ -58,13 +48,40 @@
margin-left: 2em
&:last-child
@include scroll-shadow-cover(right, var(--color-back))
padding-right: 2rem
&:first-child
@include scroll-shadow-cover(left, var(--color-back))
padding-left: 2rem
.menu
display: flex
flex-flow: row nowrap
margin-left: 0.5rem
.dropdown
font-family: var(--font-secondary)
font-size: 1.6rem
font-weight: bold
color: var(--color-theme)
text-transform: uppercase
background: transparent
border: 0
margin-right: 0.5rem
.is-active
color: var(--color-dark)
pointer-events: none
.search
display: inline-flex
align-items: center
height: 100%
padding-right: 2rem
@include breakpoint(max, xs)
.list
display: none
@include breakpoint(min, sm)
.dropdown
display: none

View File

@ -0,0 +1,57 @@
@import base
.root
font: var(--font-size-sm)/var(--line-height-md) var(--font-primary)
border: 1px solid var(--color-subtle)
border-radius: 2em
max-width: 100%
display: flex
flex-flow: row nowrap
&:focus-within
border-color: var(--color-theme)
box-shadow: 0 0 0 1px var(--color-theme)
.input
width: 100%
max-width: 100%
font: inherit
padding: 0.5rem 1rem 0.5rem 0.5rem
.icon
display: inline-block
color: var(--color-subtle-dark)
padding-left: 0.5rem
position: relative
top: 0.25rem
cursor: pointer
/* Algolia DocSearch */
@include breakpoint(max, xs)
\:global(.algolia-autocomplete .ds-dropdown-menu)
max-width: 90vw
min-width: 90vw
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--category-header)
display: block
font: bold var(--font-size-lg)/var(--line-height-md) var(--font-secondary)
text-transform: uppercase
color: var(--color-theme)
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column)
color: var(--color-dark)
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--title)
font-size: var(--font-size-sm)
\:global(.algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content)
background: var(--color-subtle-opaque) !important
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--text)
font-size: var(--font-size-sm)
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--highlight)
box-shadow: none !important
background: var(--color-theme-opaque) !important
color: var(--color-theme-dark) !important

View File

@ -30,6 +30,7 @@ import Tag from '../components/tag'
import Grid from '../components/grid'
import { YouTube, SoundCloud, Iframe, Image } from '../components/embed'
import Alert from '../components/alert'
import Search from '../components/search'
const mdxComponents = {
a: Link,
@ -119,7 +120,12 @@ class Layout extends React.Component {
bodyClass={bodyClass}
/>
<AlertSpace />
<Navigation title={meta.title} items={meta.navigation} section={section}>
<Navigation
title={meta.title}
items={meta.navigation}
section={section}
search={<Search settings={meta.docSearch} />}
>
<Progress key={location.href} />
</Navigation>
{isDocs ? (
@ -184,6 +190,10 @@ export const pageQuery = graphql`
text
url
}
docSearch {
apiKey
indexName
}
}
}
file(fields: { slug: { eq: $slug } }) {