1
0
mirror of https://github.com/kremalicious/blog.git synced 2025-01-03 02:15:08 +01:00

fix search

This commit is contained in:
Matthias Kretschmann 2019-10-02 20:48:59 +02:00
parent 3229db8143
commit 495aba8e96
Signed by: m
GPG Key ID: 606EEEF3C479A91F
13 changed files with 200 additions and 166 deletions

View File

@ -1,4 +1,6 @@
plugins/gatsby-redirect-from
node_modules
public
.cache
node_modules/
.cache/
static/
public/
coverage/

View File

@ -1,5 +0,0 @@
node_modules/
.cache/
static/
public/
coverage/

View File

@ -1,7 +1,7 @@
dist: xenial
language: node_js
node_js:
- '11'
- '12'
git:
depth: 10

View File

@ -15,7 +15,7 @@
"rename:scrypt": "sed -i -e 's|./build/Release/scrypt|scrypt|g' node_modules/scrypt/index.js",
"copy": "cp -R content/media/ public",
"lint": "run-p --continue-on-error lint:js lint:css lint:md",
"lint:js": "eslint --ignore-path .gitignore --ignore-path .prettierignore --ext .js,.jsx,.ts,.tsx .",
"lint:js": "eslint --ignore-path .gitignore --ext .js,.jsx,.ts,.tsx .",
"lint:css": "stylelint 'src/**/*.{css,scss}'",
"lint:md": "markdownlint './**/*.{md,markdown}' --ignore './{node_modules,public,.cache,.git,coverage}/**/*'",
"format": "npm run lint:js -- --fix && npm run lint:css -- --fix",

View File

@ -1,87 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import { CSSTransition } from 'react-transition-group'
import SearchInput from './SearchInput'
import SearchButton from './SearchButton'
import SearchResults from './SearchResults'
import styles from './Search.module.scss'
export default class Search extends PureComponent<
{},
{ searchOpen: boolean; query: string; results: string[] }
> {
state = {
searchOpen: false,
query: '',
results: []
}
static propTypes = {
lng: PropTypes.string.isRequired
}
toggleSearch = () => {
this.setState(prevState => ({
searchOpen: !prevState.searchOpen
}))
}
getSearchResults(query: string) {
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: any) => {
const query = event.target.value
// wildcard search https://lunrjs.com/guides/searching.html#wildcards
const results = query.length > 1 ? this.getSearchResults(`${query}*`) : []
this.setState({
results,
query
})
}
render() {
const { searchOpen, query, results } = this.state
return (
<>
<SearchButton onClick={this.toggleSearch} />
{searchOpen && (
<>
<Helmet>
<body className="hasSearchOpen" />
</Helmet>
<CSSTransition
appear={searchOpen}
in={searchOpen}
timeout={200}
classNames={styles}
>
<section className={styles.search}>
<SearchInput
value={query}
onChange={() => this.search}
onToggle={this.toggleSearch}
/>
</section>
</CSSTransition>
<SearchResults
searchQuery={query}
results={results}
toggleSearch={this.toggleSearch}
/>
</>
)}
</>
)
}
}

View File

@ -9,7 +9,7 @@ export default function SearchInput({
}: {
value: string
onToggle(): void
onChange(): void
onChange(e: Event): void
}) {
return (
<>

View File

@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { graphql, StaticQuery } from 'gatsby'
import { graphql, useStaticQuery } from 'gatsby'
import Container from '../atoms/Container'
import PostTeaser from '../Post/PostTeaser'
import SearchResultsEmpty from './SearchResultsEmpty'
@ -38,42 +38,33 @@ export default function SearchResults({
results: any
toggleSearch(): void
}) {
return (
<StaticQuery
query={query}
render={data => {
const posts = data.allMarkdownRemark.edges
const data = useStaticQuery(query)
const posts = data.allMarkdownRemark.edges
// creating portal to break out of DOM node we're in
// and render the results in content container
return ReactDOM.createPortal(
<div className={styles.searchResults}>
<Container>
{results.length > 0 ? (
<ul>
{results.map(page =>
posts
.filter(post => post.node.fields.slug === page.slug)
.map(({ node }) => (
<PostTeaser
key={page.slug}
post={node}
toggleSearch={toggleSearch}
/>
))
)}
</ul>
) : (
<SearchResultsEmpty
searchQuery={searchQuery}
results={results}
/>
)}
</Container>
</div>,
document.getElementById('document')
)
}}
/>
// creating portal to break out of DOM node we're in
// and render the results in content container
return ReactDOM.createPortal(
<div className={styles.searchResults}>
<Container>
{results.length > 0 ? (
<ul>
{results.map(page =>
posts
.filter(post => post.node.fields.slug === page.slug)
.map(({ node }: { node: any }) => (
<PostTeaser
key={page.slug}
post={node}
toggleSearch={toggleSearch}
/>
))
)}
</ul>
) : (
<SearchResultsEmpty searchQuery={searchQuery} results={results} />
)}
</Container>
</div>,
document.getElementById('document')
)
}

View File

@ -0,0 +1,45 @@
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Search from '.'
import { useStaticQuery } from 'gatsby'
describe('Search', () => {
beforeEach(() => {
useStaticQuery.mockImplementation(() => {
return {
allMarkdownRemark: {
edges: [
{
node: {
id: 'ddd',
frontmatter: {
title: 'Hello',
image: {
childImageSharp: 'hello'
}
},
fields: {
slug: '/hello/'
}
}
}
]
}
}
})
const portalRoot = document.createElement('div')
portalRoot.setAttribute('id', 'document')
document.body.appendChild(portalRoot)
})
it('can be opened', () => {
const { getByTitle, getByPlaceholderText } = render(<Search lng="en" />)
fireEvent.click(getByTitle('Search'))
fireEvent.change(getByPlaceholderText('Search everything'), {
target: { value: 'hello' }
})
fireEvent.click(getByTitle('Close search'))
})
})

View File

@ -0,0 +1,68 @@
import React, { useState } from 'react'
import Helmet from 'react-helmet'
import { CSSTransition } from 'react-transition-group'
import SearchInput from './SearchInput'
import SearchButton from './SearchButton'
import SearchResults from './SearchResults'
import styles from './index.module.scss'
function getSearchResults(query: string, lng: string) {
if (!query || !window.__LUNR__) return []
const lunrIndex = window.__LUNR__[lng]
const results = lunrIndex.index.search(query)
return results.map(({ ref }: { ref: string }) => lunrIndex.store[ref])
}
export default function Search({ lng }: { lng: string }) {
const [searchOpen, setSearchOpen] = useState(false)
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const toggleSearch = () => {
setSearchOpen(!searchOpen)
}
const search = (event: any) => {
const query = event.target.value
// wildcard search https://lunrjs.com/guides/searching.html#wildcards
const results = query.length > 1 ? getSearchResults(`${query}*`, lng) : []
setQuery(query)
setResults(results)
}
return (
<>
<SearchButton onClick={toggleSearch} />
{searchOpen && (
<>
<Helmet>
<body className="hasSearchOpen" />
</Helmet>
<CSSTransition
appear={searchOpen}
in={searchOpen}
timeout={200}
classNames={styles}
>
<section className={styles.search}>
<SearchInput
value={query}
onChange={(e: Event) => search(e)}
onToggle={toggleSearch}
/>
</section>
</CSSTransition>
<SearchResults
searchQuery={query}
results={results}
toggleSearch={toggleSearch}
/>
</>
)}
</>
)
}

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { useState } from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import PostTeaser from '../Post/PostTeaser'
import styles from './RelatedPosts.module.scss'
@ -51,7 +51,13 @@ const postsWithDataFilter = (
export default function RelatedPosts({ tags }: { tags: string[] }) {
const data = useStaticQuery(query)
const posts = data.allMarkdownRemark.edges
const filteredPosts = postsWithDataFilter(posts, 'tags', tags)
const [filteredPosts, setFilteredPosts] = useState(
postsWithDataFilter(posts, 'tags', tags)
)
function refreshPosts() {
setFilteredPosts(postsWithDataFilter(posts, 'tags', tags))
}
return (
<aside className={styles.relatedPosts}>
@ -64,7 +70,9 @@ export default function RelatedPosts({ tags }: { tags: string[] }) {
<PostTeaser key={node.id} post={node} />
))}
</ul>
<button className={`${styles.button} btn`}>Refresh Related Posts</button>
<button className={`${styles.button} btn`} onClick={refreshPosts}>
Refresh Related Posts
</button>
</aside>
)
}

View File

@ -10,9 +10,45 @@ import { ReactComponent as Bitcoin } from '../../images/bitcoin.svg'
import styles from './Footer.module.scss'
import { useSiteMetadata } from '../../hooks/use-site-metadata'
export default function Footer() {
const { name, uri, bitcoin, github } = useSiteMetadata()
function Copyright({
toggleModal,
showModal
}: {
toggleModal(): void
showModal: boolean
}) {
const { name, uri, bitcoin, github } = useSiteMetadata().author
const year = new Date().getFullYear()
return (
<section className={styles.copyright}>
<p>
&copy; 2005&ndash;
{year + ' '}
<a href={uri} rel="me">
{name}
</a>
</p>
<p>
<a href={`${github}/blog`}>
<Github />
View source
</a>
<button className={styles.btc} onClick={toggleModal}>
<Bitcoin />
<code>{bitcoin}</code>
</button>
</p>
{showModal && (
<ModalThanks isOpen={showModal} handleCloseModal={toggleModal} />
)}
</section>
)
}
export default function Footer() {
const [showModal, setShowModal] = useState(false)
const toggleModal = () => {
@ -24,31 +60,7 @@ export default function Footer() {
<Container>
<Vcard />
<Subscribe />
<section className={styles.copyright}>
<p>
&copy; 2005&ndash;
{year + ' '}
<a href={uri} rel="me">
{name}
</a>
</p>
<p>
<a href={`${github}/blog`}>
<Github />
View source
</a>
<button className={styles.btc} onClick={toggleModal}>
<Bitcoin />
<code>{bitcoin}</code>
</button>
</p>
{showModal && (
<ModalThanks isOpen={showModal} handleCloseModal={toggleModal} />
)}
</section>
<Copyright showModal={showModal} toggleModal={toggleModal} />
</Container>
</footer>
)

View File

@ -1,7 +1,7 @@
import React from 'react'
import { Link } from 'gatsby'
import Container from '../atoms/Container'
import Search from '../Search/Search'
import Search from '../Search'
import Menu from '../molecules/Menu'
import styles from './Header.module.scss'