mirror of
https://github.com/kremalicious/blog.git
synced 2025-01-05 03:15:07 +01:00
make search functional
This commit is contained in:
parent
e708904f64
commit
366f9c5ea4
@ -70,6 +70,39 @@ module.exports = {
|
|||||||
includePaths: [`${__dirname}/node_modules`, `${__dirname}/src/styles`]
|
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-plugin-react-helmet',
|
||||||
'gatsby-transformer-yaml',
|
'gatsby-transformer-yaml',
|
||||||
'gatsby-transformer-sharp',
|
'gatsby-transformer-sharp',
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"gatsby": "^2.0.0-rc.0",
|
"gatsby": "^2.0.0-rc.0",
|
||||||
"gatsby-image": "^2.0.0-rc.0",
|
"gatsby-image": "^2.0.0-rc.0",
|
||||||
"gatsby-plugin-catch-links": "^2.0.2-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-matomo": "^0.4.1",
|
||||||
"gatsby-plugin-react-helmet": "^3.0.0-rc.0",
|
"gatsby-plugin-react-helmet": "^3.0.0-rc.0",
|
||||||
"gatsby-plugin-sass": "^2.0.0-rc.0",
|
"gatsby-plugin-sass": "^2.0.0-rc.0",
|
||||||
|
@ -11,7 +11,7 @@ const Layout = ({ children }) => (
|
|||||||
<Head />
|
<Head />
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main className={styles.document}>
|
<main className={styles.document} id="document">
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<Container>{children}</Container>
|
<Container>{children}</Container>
|
||||||
</div>
|
</div>
|
||||||
|
11
src/components/atoms/SearchButton.jsx
Normal file
11
src/components/atoms/SearchButton.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import SearchIcon from '../svg/MagnifyingGlass'
|
||||||
|
import styles from './SearchButton.module.scss'
|
||||||
|
|
||||||
|
const SearchButton = props => (
|
||||||
|
<button type="button" className={styles.searchButton} {...props}>
|
||||||
|
<SearchIcon />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SearchButton
|
33
src/components/atoms/SearchButton.module.scss
Normal file
33
src/components/atoms/SearchButton.module.scss
Normal file
@ -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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/components/atoms/SearchInput.jsx
Normal file
14
src/components/atoms/SearchInput.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React, { Fragment } from 'react'
|
||||||
|
import Input from './Input'
|
||||||
|
import styles from './SearchInput.module.scss'
|
||||||
|
|
||||||
|
const SearchInput = props => (
|
||||||
|
<Fragment>
|
||||||
|
<Input autoFocus type="search" placeholder="Search everything" {...props} />
|
||||||
|
<button className={styles.searchInputClose} onClick={props.onToggle}>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SearchInput
|
7
src/components/atoms/SearchInput.module.scss
Normal file
7
src/components/atoms/SearchInput.module.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@import 'variables';
|
||||||
|
|
||||||
|
.searchInputClose {
|
||||||
|
position: absolute;
|
||||||
|
right: $spacer / 4;
|
||||||
|
top: $spacer / 4;
|
||||||
|
}
|
30
src/components/atoms/SearchResults.jsx
Normal file
30
src/components/atoms/SearchResults.jsx
Normal file
@ -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(
|
||||||
|
<div className={styles.searchResults}>
|
||||||
|
<Container>
|
||||||
|
<ul>
|
||||||
|
{results.map(page => (
|
||||||
|
<li key={page.url}>
|
||||||
|
<Link to={page.url} onClick={onClose}>
|
||||||
|
{page.title}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Container>
|
||||||
|
</div>,
|
||||||
|
document.getElementById('document')
|
||||||
|
)
|
||||||
|
|
||||||
|
SearchResults.propTypes = {
|
||||||
|
results: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchResults
|
28
src/components/atoms/SearchResults.module.scss
Normal file
28
src/components/atoms/SearchResults.module.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react'
|
import React, { PureComponent, Fragment } from 'react'
|
||||||
// import { Link } from 'gatsby'
|
import PropTypes from 'prop-types'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
import { CSSTransition } from 'react-transition-group'
|
import { CSSTransition } from 'react-transition-group'
|
||||||
import Input from '../atoms/Input'
|
import SearchInput from '../atoms/SearchInput'
|
||||||
import SearchIcon from '../svg/MagnifyingGlass'
|
import SearchButton from '../atoms/SearchButton'
|
||||||
|
import SearchResults from '../atoms/SearchResults'
|
||||||
|
|
||||||
import styles from './Search.module.scss'
|
import styles from './Search.module.scss'
|
||||||
|
|
||||||
class Search extends PureComponent {
|
class Search extends PureComponent {
|
||||||
@ -11,7 +13,9 @@ class Search extends PureComponent {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
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
|
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() {
|
render() {
|
||||||
|
const { searchOpen, query, results } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<body className={this.isSearchOpen() ? 'has-search-open' : null} />
|
<body className={this.isSearchOpen() ? 'has-search-open' : null} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<button
|
<SearchButton onClick={this.toggleSearch} />
|
||||||
type="button"
|
|
||||||
className={styles.searchButton}
|
|
||||||
onClick={this.toggleSearch}
|
|
||||||
>
|
|
||||||
<SearchIcon />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{this.state.searchOpen && (
|
{searchOpen && (
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
appear={this.state.searchOpen}
|
appear={searchOpen}
|
||||||
in={this.state.searchOpen}
|
in={searchOpen}
|
||||||
timeout={200}
|
timeout={200}
|
||||||
classNames={styles}
|
classNames={styles}
|
||||||
>
|
>
|
||||||
<section className={styles.search}>
|
<section className={styles.search}>
|
||||||
<Input
|
<SearchInput
|
||||||
autoFocus
|
value={query}
|
||||||
type="search"
|
onChange={this.search}
|
||||||
placeholder="Search everything"
|
onToggle={this.closeSearch}
|
||||||
onBlur={this.toggleSearch}
|
|
||||||
// value={this.state.query}
|
|
||||||
// onChange={this.search}
|
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
className={styles.searchInputClose}
|
|
||||||
onClick={this.toggleSearch}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</section>
|
</section>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{query && (
|
||||||
|
<SearchResults results={results} onClose={this.closeSearch} />
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Search.propTypes = {
|
||||||
|
lng: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
export default Search
|
export default Search
|
||||||
|
@ -1,37 +1,5 @@
|
|||||||
@import 'variables';
|
@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 {
|
.search {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: $spacer / 2;
|
left: $spacer / 2;
|
||||||
@ -49,12 +17,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchInputClose {
|
|
||||||
position: absolute;
|
|
||||||
right: $spacer / 4;
|
|
||||||
top: $spacer / 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appear,
|
.appear,
|
||||||
.enter {
|
.enter {
|
||||||
opacity: .01;
|
opacity: .01;
|
||||||
|
@ -19,7 +19,7 @@ class Header extends PureComponent {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<nav role="navigation" className={styles.nav}>
|
<nav role="navigation" className={styles.nav}>
|
||||||
<Search />
|
<Search lng="en" />
|
||||||
<Menu />
|
<Menu />
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,6 +76,7 @@ export const pageQuery = graphql`
|
|||||||
slug
|
slug
|
||||||
date(formatString: "MMMM DD, YYYY")
|
date(formatString: "MMMM DD, YYYY")
|
||||||
}
|
}
|
||||||
|
rawMarkdownBody
|
||||||
}
|
}
|
||||||
|
|
||||||
contentYaml {
|
contentYaml {
|
||||||
|
Loading…
Reference in New Issue
Block a user