diff --git a/gatsby-config.js b/gatsby-config.js index ef7bfaa3..a55a2cec 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -70,6 +70,39 @@ module.exports = { includePaths: [`${__dirname}/node_modules`, `${__dirname}/src/styles`] } }, + { + resolve: 'gatsby-plugin-lunr', + options: { + languages: [ + { + // ISO 639-1 language codes. See https://lunrjs.com/guides/language_support.html for details + name: 'en' + } + ], + // 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: 'title', store: true, attributes: { boost: 20 } }, + { name: 'content' }, + { name: 'excerpt', attributes: { boost: 10 } }, + { name: 'category', store: true, attributes: { boost: 5 } }, + { name: 'tags', store: true }, + { name: 'url', store: true } + ], + // How to resolve each field's value for a supported node type + resolvers: { + // For any node of type MarkdownRemark, list how to resolve the fields' values + MarkdownRemark: { + title: node => node.frontmatter.title, + content: node => node.rawMarkdownBody, + excerpt: node => node.frontmatter.excerpt, + category: node => node.frontmatter.category, + tags: node => node.frontmatter.tags, + url: node => node.fields.slug + } + } + } + }, 'gatsby-plugin-react-helmet', 'gatsby-transformer-yaml', 'gatsby-transformer-sharp', diff --git a/package.json b/package.json index 66264012..4bcc9dd6 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "gatsby": "^2.0.0-rc.0", "gatsby-image": "^2.0.0-rc.0", "gatsby-plugin-catch-links": "^2.0.2-rc.0", + "gatsby-plugin-lunr": "^1.1.0", "gatsby-plugin-matomo": "^0.4.1", "gatsby-plugin-react-helmet": "^3.0.0-rc.0", "gatsby-plugin-sass": "^2.0.0-rc.0", diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index b93c356c..429874f8 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -11,7 +11,7 @@ const Layout = ({ children }) => (
-
+
{children}
diff --git a/src/components/atoms/SearchButton.jsx b/src/components/atoms/SearchButton.jsx new file mode 100644 index 00000000..5cd78c80 --- /dev/null +++ b/src/components/atoms/SearchButton.jsx @@ -0,0 +1,11 @@ +import React from 'react' +import SearchIcon from '../svg/MagnifyingGlass' +import styles from './SearchButton.module.scss' + +const SearchButton = props => ( + +) + +export default SearchButton diff --git a/src/components/atoms/SearchButton.module.scss b/src/components/atoms/SearchButton.module.scss new file mode 100644 index 00000000..7229a39d --- /dev/null +++ b/src/components/atoms/SearchButton.module.scss @@ -0,0 +1,33 @@ +@import 'variables'; + +.searchButton { + padding: .65rem .85rem; + text-align: center; + line-height: 1; + vertical-align: middle; + display: inline-block; + margin-right: $spacer / 4; + + &:focus { + outline: 0; + } + + svg { + fill: $text-color-light; + width: 21px; + height: 21px; + } + + &:hover, + &:focus { + svg { + fill: $brand-cyan; + } + } + + &:active { + svg { + fill: darken($brand-cyan, 30%); + } + } +} diff --git a/src/components/atoms/SearchInput.jsx b/src/components/atoms/SearchInput.jsx new file mode 100644 index 00000000..04ef9fca --- /dev/null +++ b/src/components/atoms/SearchInput.jsx @@ -0,0 +1,14 @@ +import React, { Fragment } from 'react' +import Input from './Input' +import styles from './SearchInput.module.scss' + +const SearchInput = props => ( + + + + +) + +export default SearchInput diff --git a/src/components/atoms/SearchInput.module.scss b/src/components/atoms/SearchInput.module.scss new file mode 100644 index 00000000..808c2cba --- /dev/null +++ b/src/components/atoms/SearchInput.module.scss @@ -0,0 +1,7 @@ +@import 'variables'; + +.searchInputClose { + position: absolute; + right: $spacer / 4; + top: $spacer / 4; +} diff --git a/src/components/atoms/SearchResults.jsx b/src/components/atoms/SearchResults.jsx new file mode 100644 index 00000000..65d9c97e --- /dev/null +++ b/src/components/atoms/SearchResults.jsx @@ -0,0 +1,30 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import PropTypes from 'prop-types' +import { Link } from 'gatsby' +import Container from '../atoms/Container' +import styles from './SearchResults.module.scss' + +const SearchResults = ({ results, onClose }) => + ReactDOM.createPortal( +
+ +
    + {results.map(page => ( +
  • + + {page.title} + +
  • + ))} +
+
+
, + document.getElementById('document') + ) + +SearchResults.propTypes = { + results: PropTypes.array.isRequired +} + +export default SearchResults diff --git a/src/components/atoms/SearchResults.module.scss b/src/components/atoms/SearchResults.module.scss new file mode 100644 index 00000000..e32e90b0 --- /dev/null +++ b/src/components/atoms/SearchResults.module.scss @@ -0,0 +1,28 @@ +@import 'variables'; +@import 'mixins'; + +.searchResults { + position: absolute; + left: 0; + right: 0; + z-index: 10; + top: 0; + bottom: 0; + background: $body-background-color; + + ul { + @include breakoutviewport; + + margin-top: $spacer; + + li::before { + top: $spacer / 7; + } + } + + a { + padding-top: $spacer / 6; + padding-bottom: $spacer / 6; + display: inline-block; + } +} diff --git a/src/components/molecules/Search.jsx b/src/components/molecules/Search.jsx index f38b365e..9f62f552 100644 --- a/src/components/molecules/Search.jsx +++ b/src/components/molecules/Search.jsx @@ -1,9 +1,11 @@ import React, { PureComponent, Fragment } from 'react' -// import { Link } from 'gatsby' +import PropTypes from 'prop-types' import Helmet from 'react-helmet' import { CSSTransition } from 'react-transition-group' -import Input from '../atoms/Input' -import SearchIcon from '../svg/MagnifyingGlass' +import SearchInput from '../atoms/SearchInput' +import SearchButton from '../atoms/SearchButton' +import SearchResults from '../atoms/SearchResults' + import styles from './Search.module.scss' class Search extends PureComponent { @@ -11,7 +13,9 @@ class Search extends PureComponent { super(props) this.state = { - searchOpen: false + searchOpen: false, + query: '', + results: [] } } @@ -21,51 +25,70 @@ class Search extends PureComponent { })) } + closeSearch = () => { + this.setState({ + searchOpen: false, + query: '', + results: [] + }) + } + isSearchOpen = () => this.state.searchOpen === true + getSearchResults(query) { + if (!query || !window.__LUNR__) return [] + const lunrIndex = window.__LUNR__[this.props.lng] + const results = lunrIndex.index.search(query) + return results.map(({ ref }) => lunrIndex.store[ref]) + } + + search = event => { + const query = event.target.value + const results = this.getSearchResults(query) + this.setState({ + results, + query + }) + } + render() { + const { searchOpen, query, results } = this.state + return ( - + - {this.state.searchOpen && ( + {searchOpen && (
- -
)} + + {query && ( + + )}
) } } +Search.propTypes = { + lng: PropTypes.string.isRequired +} + export default Search diff --git a/src/components/molecules/Search.module.scss b/src/components/molecules/Search.module.scss index 94ea83ee..74e2792e 100644 --- a/src/components/molecules/Search.module.scss +++ b/src/components/molecules/Search.module.scss @@ -1,37 +1,5 @@ @import 'variables'; -.searchButton { - padding: .65rem .85rem; - text-align: center; - line-height: 1; - vertical-align: middle; - display: inline-block; - margin-right: $spacer / 4; - - &:focus { - outline: 0; - } - - svg { - fill: $text-color-light; - width: 21px; - height: 21px; - } - - &:hover, - &:focus { - svg { - fill: $brand-cyan; - } - } - - &:active { - svg { - fill: darken($brand-cyan, 30%); - } - } -} - .search { position: absolute; left: $spacer / 2; @@ -49,12 +17,6 @@ } } -.searchInputClose { - position: absolute; - right: $spacer / 4; - top: $spacer / 4; -} - .appear, .enter { opacity: .01; diff --git a/src/components/organisms/Header.jsx b/src/components/organisms/Header.jsx index 510d3991..0330aa68 100644 --- a/src/components/organisms/Header.jsx +++ b/src/components/organisms/Header.jsx @@ -19,7 +19,7 @@ class Header extends PureComponent { diff --git a/src/templates/Post.jsx b/src/templates/Post.jsx index b9392b99..4499706f 100644 --- a/src/templates/Post.jsx +++ b/src/templates/Post.jsx @@ -76,6 +76,7 @@ export const pageQuery = graphql` slug date(formatString: "MMMM DD, YYYY") } + rawMarkdownBody } contentYaml {