From 495aba8e96d95db58b0b36216d87cea4fb487a4e Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Wed, 2 Oct 2019 20:48:59 +0200 Subject: [PATCH] fix search --- .eslintignore | 8 +- .prettierignore | 5 -- .travis.yml | 2 +- package.json | 2 +- src/components/Search/Search.tsx | 87 ------------------- src/components/Search/SearchInput.tsx | 2 +- src/components/Search/SearchResults.tsx | 65 ++++++-------- .../{Search.module.scss => index.module.scss} | 0 src/components/Search/index.test.tsx | 45 ++++++++++ src/components/Search/index.tsx | 68 +++++++++++++++ src/components/molecules/RelatedPosts.tsx | 14 ++- src/components/organisms/Footer.tsx | 66 ++++++++------ src/components/organisms/Header.tsx | 2 +- 13 files changed, 200 insertions(+), 166 deletions(-) delete mode 100644 .prettierignore delete mode 100644 src/components/Search/Search.tsx rename src/components/Search/{Search.module.scss => index.module.scss} (100%) create mode 100644 src/components/Search/index.test.tsx create mode 100644 src/components/Search/index.tsx diff --git a/.eslintignore b/.eslintignore index ea92f360..f490b26f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,6 @@ plugins/gatsby-redirect-from -node_modules -public -.cache +node_modules/ +.cache/ +static/ +public/ +coverage/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 220f5418..00000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -.cache/ -static/ -public/ -coverage/ diff --git a/.travis.yml b/.travis.yml index b99c7822..11d4cf34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ dist: xenial language: node_js node_js: - - '11' + - '12' git: depth: 10 diff --git a/package.json b/package.json index adbd13f7..dd9b110d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx deleted file mode 100644 index bac45bcd..00000000 --- a/src/components/Search/Search.tsx +++ /dev/null @@ -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 ( - <> - - - {searchOpen && ( - <> - - - - - -
- this.search} - onToggle={this.toggleSearch} - /> -
-
- - - - )} - - ) - } -} diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index fe84e626..0ee9427e 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -9,7 +9,7 @@ export default function SearchInput({ }: { value: string onToggle(): void - onChange(): void + onChange(e: Event): void }) { return ( <> diff --git a/src/components/Search/SearchResults.tsx b/src/components/Search/SearchResults.tsx index 4d0c62ac..f65fdc2b 100644 --- a/src/components/Search/SearchResults.tsx +++ b/src/components/Search/SearchResults.tsx @@ -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 ( - { - 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( -
- - {results.length > 0 ? ( -
    - {results.map(page => - posts - .filter(post => post.node.fields.slug === page.slug) - .map(({ node }) => ( - - )) - )} -
- ) : ( - - )} -
-
, - document.getElementById('document') - ) - }} - /> + // creating portal to break out of DOM node we're in + // and render the results in content container + return ReactDOM.createPortal( +
+ + {results.length > 0 ? ( +
    + {results.map(page => + posts + .filter(post => post.node.fields.slug === page.slug) + .map(({ node }: { node: any }) => ( + + )) + )} +
+ ) : ( + + )} +
+
, + document.getElementById('document') ) } diff --git a/src/components/Search/Search.module.scss b/src/components/Search/index.module.scss similarity index 100% rename from src/components/Search/Search.module.scss rename to src/components/Search/index.module.scss diff --git a/src/components/Search/index.test.tsx b/src/components/Search/index.test.tsx new file mode 100644 index 00000000..8b4b4810 --- /dev/null +++ b/src/components/Search/index.test.tsx @@ -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() + fireEvent.click(getByTitle('Search')) + fireEvent.change(getByPlaceholderText('Search everything'), { + target: { value: 'hello' } + }) + fireEvent.click(getByTitle('Close search')) + }) +}) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx new file mode 100644 index 00000000..f3a4be52 --- /dev/null +++ b/src/components/Search/index.tsx @@ -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 ( + <> + + + {searchOpen && ( + <> + + + + + +
+ search(e)} + onToggle={toggleSearch} + /> +
+
+ + + + )} + + ) +} diff --git a/src/components/molecules/RelatedPosts.tsx b/src/components/molecules/RelatedPosts.tsx index f18a1700..466508f1 100644 --- a/src/components/molecules/RelatedPosts.tsx +++ b/src/components/molecules/RelatedPosts.tsx @@ -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 ( ) } diff --git a/src/components/organisms/Footer.tsx b/src/components/organisms/Footer.tsx index 99209568..be7b8712 100644 --- a/src/components/organisms/Footer.tsx +++ b/src/components/organisms/Footer.tsx @@ -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 ( +
+

+ © 2005– + {year + ' '} + + {name} + +

+ +

+ + + View source + + +

+ + {showModal && ( + + )} +
+ ) +} + +export default function Footer() { const [showModal, setShowModal] = useState(false) const toggleModal = () => { @@ -24,31 +60,7 @@ export default function Footer() { - -
-

- © 2005– - {year + ' '} - - {name} - -

- -

- - - View source - - -

- - {showModal && ( - - )} -
+
) diff --git a/src/components/organisms/Header.tsx b/src/components/organisms/Header.tsx index ab67ea0e..d04a61f7 100644 --- a/src/components/organisms/Header.tsx +++ b/src/components/organisms/Header.tsx @@ -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'