mirror of
https://github.com/kremalicious/blog.git
synced 2025-01-03 02:15:08 +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`]
|
||||
}
|
||||
},
|
||||
{
|
||||
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',
|
||||
|
@ -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",
|
||||
|
@ -11,7 +11,7 @@ const Layout = ({ children }) => (
|
||||
<Head />
|
||||
<Header />
|
||||
|
||||
<main className={styles.document}>
|
||||
<main className={styles.document} id="document">
|
||||
<div className={styles.content}>
|
||||
<Container>{children}</Container>
|
||||
</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 { 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 (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<body className={this.isSearchOpen() ? 'has-search-open' : null} />
|
||||
</Helmet>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.searchButton}
|
||||
onClick={this.toggleSearch}
|
||||
>
|
||||
<SearchIcon />
|
||||
</button>
|
||||
<SearchButton onClick={this.toggleSearch} />
|
||||
|
||||
{this.state.searchOpen && (
|
||||
{searchOpen && (
|
||||
<CSSTransition
|
||||
appear={this.state.searchOpen}
|
||||
in={this.state.searchOpen}
|
||||
appear={searchOpen}
|
||||
in={searchOpen}
|
||||
timeout={200}
|
||||
classNames={styles}
|
||||
>
|
||||
<section className={styles.search}>
|
||||
<Input
|
||||
autoFocus
|
||||
type="search"
|
||||
placeholder="Search everything"
|
||||
onBlur={this.toggleSearch}
|
||||
// value={this.state.query}
|
||||
// onChange={this.search}
|
||||
<SearchInput
|
||||
value={query}
|
||||
onChange={this.search}
|
||||
onToggle={this.closeSearch}
|
||||
/>
|
||||
<button
|
||||
className={styles.searchInputClose}
|
||||
onClick={this.toggleSearch}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</section>
|
||||
</CSSTransition>
|
||||
)}
|
||||
|
||||
{query && (
|
||||
<SearchResults results={results} onClose={this.closeSearch} />
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Search.propTypes = {
|
||||
lng: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default Search
|
||||
|
@ -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;
|
||||
|
@ -19,7 +19,7 @@ class Header extends PureComponent {
|
||||
</h1>
|
||||
|
||||
<nav role="navigation" className={styles.nav}>
|
||||
<Search />
|
||||
<Search lng="en" />
|
||||
<Menu />
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -76,6 +76,7 @@ export const pageQuery = graphql`
|
||||
slug
|
||||
date(formatString: "MMMM DD, YYYY")
|
||||
}
|
||||
rawMarkdownBody
|
||||
}
|
||||
|
||||
contentYaml {
|
||||
|
Loading…
Reference in New Issue
Block a user