From 6d02cad50d5b6ea80acd92d9ce220ccfc93c47fb Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Sat, 11 Jul 2020 18:08:02 +0300 Subject: [PATCH] added search --- gatsby-config.js | 50 +++++++++++++++++++ package-lock.json | 47 +++++++++++++++++ package.json | 5 +- src/components/Article/Article.js | 4 +- .../Articles/Categories/Categories.js | 4 +- src/components/Articles/Search/Search.js | 21 ++++++-- src/components/Header/Header.js | 2 +- src/components/SEO/SEO.js | 8 +-- src/helpers/toFrontmatter.js | 13 +++++ src/images/search-icon.svg | 1 + src/pages/404.js | 2 + src/pages/articles.js | 35 +++++++++++-- src/pages/index.js | 2 +- src/templates/articleTemplate.js | 2 +- src/templates/categoryTemplate.js | 4 +- src/templates/tagTemplate.js | 4 +- 16 files changed, 179 insertions(+), 25 deletions(-) create mode 100644 src/helpers/toFrontmatter.js create mode 100644 src/images/search-icon.svg diff --git a/gatsby-config.js b/gatsby-config.js index aba38cd..1e300ce 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -130,6 +130,56 @@ module.exports = { ], }, }, + { + resolve: 'gatsby-plugin-local-search', + options: { + name: 'pages', + engine: 'flexsearch', + engineOptions: 'speed', + query: ` + { + allMarkdownRemark(filter: { frontmatter: { templateKey: { eq: "article" } } }) { + nodes { + id + frontmatter { + slug + title + category + image { + childImageSharp { + fluid(maxWidth: 960) { + aspectRatio + base64 + sizes + src + srcSet + } + } + } + tags + description + } + rawMarkdownBody + } + } + } + `, + ref: 'id', + index: ['title', 'category', 'body', 'tags', 'description'], + store: ['id', 'slug', 'title', 'category', 'tags', 'description', 'image'], + normalizer: ({ data }) => + data.allMarkdownRemark.nodes.map(node => ({ + id: node.id, + slug: node.frontmatter.slug, + title: node.frontmatter.title, + category: node.frontmatter.category, + description: node.frontmatter.description, + image: node.frontmatter.image, + tags: node.frontmatter.tags, + body: node.rawMarkdownBody, + })), + }, + }, { resolve: 'gatsby-plugin-react-svg', options: { diff --git a/package-lock.json b/package-lock.json index b99173d..7182506 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8227,6 +8227,11 @@ "normalize.css": "^7.0.0" } }, + "flexsearch": { + "version": "0.6.32", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.6.32.tgz", + "integrity": "sha512-EF1BWkhwoeLtbIlDbY/vDSLBen/E5l/f1Vg7iX5CDymQCamcx1vhlc3tIZxIDplPjgi0jhG37c67idFbjg+v+Q==" + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -8717,6 +8722,25 @@ "micromatch": "^3.1.10" } }, + "gatsby-plugin-local-search": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gatsby-plugin-local-search/-/gatsby-plugin-local-search-1.1.1.tgz", + "integrity": "sha512-OU0IR0Gi6ExZ3bzB0j6pR1rnnBPNGh3BEoRDAfvDeP9+U9rqzrPV053cr+Ueq0snEnlisPXu+vIv9Hd04esPnw==", + "requires": { + "flexsearch": "^0.6.32", + "lodash.lowerfirst": "^4.3.1", + "lodash.pick": "^4.4.0", + "lunr": "^2.3.8", + "pascalcase": "^1.0.0" + }, + "dependencies": { + "pascalcase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-1.0.0.tgz", + "integrity": "sha512-BSExi0rRnCHReJys6NocaK+cfTXNinAegfWBvr0JD3hiaEG7Nuc7r0CIdOJunXrs8gU/sbHQ9dxVAtiVQisjmg==" + } + } + }, "gatsby-plugin-manifest": { "version": "2.4.17", "resolved": "https://registry.npmjs.org/gatsby-plugin-manifest/-/gatsby-plugin-manifest-2.4.17.tgz", @@ -12703,6 +12727,11 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "lodash.lowerfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.lowerfirst/-/lodash.lowerfirst-4.3.1.tgz", + "integrity": "sha1-3jx7EuAsZSSgBZwvbLfFxSZVoT0=" + }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -12718,6 +12747,11 @@ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -12908,6 +12942,11 @@ "yallist": "^2.0.0" } }, + "lunr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.8.tgz", + "integrity": "sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg==" + }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -17027,6 +17066,14 @@ } } }, + "react-use-flexsearch": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/react-use-flexsearch/-/react-use-flexsearch-0.1.1.tgz", + "integrity": "sha512-UDRDB26HPcAo0gXNkUYYkcjoYCW4FSWr7Ich4adgVr7bqefJG7fnjlcqnwsKQkbZuteRLYzzox+1FKRTt3Z5vg==", + "requires": { + "flexsearch": "^0.6.22" + } + }, "react-virtualized-auto-sizer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz", diff --git a/package.json b/package.json index 13dfec4..1733519 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "esm": "^3.2.25", "gatsby": "^2.23.12", "gatsby-image": "^2.4.12", + "gatsby-plugin-local-search": "^1.1.1", "gatsby-plugin-manifest": "^2.4.17", "gatsby-plugin-netlify-cms": "^4.3.9", "gatsby-plugin-react-helmet": "^3.3.8", @@ -36,11 +37,13 @@ "node-sass": "^4.14.1", "prismjs": "^1.20.0", "prop-types": "^15.7.2", + "query-string": "^6.13.1", "react": "^16.12.0", "react-dom": "^16.12.0", "react-flexbox-grid": "^2.1.2", "react-github-btn": "^1.2.0", - "react-helmet": "^6.1.0" + "react-helmet": "^6.1.0", + "react-use-flexsearch": "^0.1.1" }, "devDependencies": { "prettier": "2.0.5" diff --git a/src/components/Article/Article.js b/src/components/Article/Article.js index 37c22f1..da675e1 100644 --- a/src/components/Article/Article.js +++ b/src/components/Article/Article.js @@ -16,7 +16,7 @@ const Article = ({ data: { slug, keywords, title, image, description, category, const { siteMetadata: { siteUrl } } = useSiteMetadataQuery() return ( <> - +
@@ -31,7 +31,7 @@ const Article = ({ data: { slug, keywords, title, image, description, category,
- {new Date(date).toLocaleDateString()} + {date}
diff --git a/src/components/Articles/Categories/Categories.js b/src/components/Articles/Categories/Categories.js index 91dceb6..1760a21 100644 --- a/src/components/Articles/Categories/Categories.js +++ b/src/components/Articles/Categories/Categories.js @@ -1,10 +1,10 @@ import React from 'react'; import { Link } from 'gatsby' -const Categories = ({ children }) => { +const Categories = ({ children, pathname }) => { return (
-
+
Все
{children} diff --git a/src/components/Articles/Search/Search.js b/src/components/Articles/Search/Search.js index 3a74707..8aefacc 100644 --- a/src/components/Articles/Search/Search.js +++ b/src/components/Articles/Search/Search.js @@ -1,13 +1,26 @@ import React from 'react' +import { navigate } from 'gatsby' import PropTypes from 'prop-types' -const Search = ({ articlesLength }) => { +const Search = ({ articlesLength, resultsLength, query, setQuery }) => { + const handleInput = e => { + const query = e.target.value + if (query === '') { + navigate('/articles') + setQuery('') + return null + } + + navigate(`/articles/?search=${query.trim()}`) + setQuery(query) + } + return (
- - {articlesLength} + handleInput(e)} value={query} placeholder="Напишите сюда чтобы отфильтровать статьи" type="text" /> + {query === '' ? articlesLength : resultsLength}
- ) + ) } export default Search diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 4f54f7f..7de2d3c 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -30,7 +30,7 @@ const Header = () => { const renderLinks = () => { return (
    - {menuLinks.map((page, i) =>
  • {page.name}
  • )} + {menuLinks.map((page, i) =>
  • {page.name}
  • )}
) } diff --git a/src/components/SEO/SEO.js b/src/components/SEO/SEO.js index 24975e2..bce35cc 100644 --- a/src/components/SEO/SEO.js +++ b/src/components/SEO/SEO.js @@ -4,12 +4,13 @@ import PropTypes from 'prop-types' import { Helmet } from 'react-helmet' import { useLocation } from '@reach/router' -const SEO = ({ title, keywords, description, image, type, canonical }) => { +const SEO = ({ title, titleTemplate, keywords, description, image, type, canonical }) => { const { pathname } = useLocation() const { siteMetadata } = useSiteMetadataQuery() const seo = { title: title || siteMetadata.title, + titleTemplate: true, siteName: siteMetadata.siteName, type: type || 'website', image: image || 'custom image url...', @@ -18,9 +19,10 @@ const SEO = ({ title, keywords, description, image, type, canonical }) => { url: `${siteMetadata.siteUrl}${pathname}`, canonical: canonical || `${siteMetadata.siteUrl}${pathname}` } - + return ( - + + {seo.keywords && ( diff --git a/src/helpers/toFrontmatter.js b/src/helpers/toFrontmatter.js new file mode 100644 index 0000000..b6239d3 --- /dev/null +++ b/src/helpers/toFrontmatter.js @@ -0,0 +1,13 @@ +const toFrontmatter = array => { + return { + edges: array.map(el => { + return { + node: { + frontmatter: el + } + } + }) + } +} + +export default toFrontmatter \ No newline at end of file diff --git a/src/images/search-icon.svg b/src/images/search-icon.svg new file mode 100644 index 0000000..a750da0 --- /dev/null +++ b/src/images/search-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/404.js b/src/pages/404.js index 80ef32e..b4c96c4 100644 --- a/src/pages/404.js +++ b/src/pages/404.js @@ -2,11 +2,13 @@ import React from 'react'; import { Main } from '../ui/ui' import { Grid } from 'react-flexbox-grid' import { Link } from 'gatsby' +import SEO from '../components/SEO/SEO' import Layout from '../components/Layout/Layout' const NotFound = () => { return ( +
diff --git a/src/pages/articles.js b/src/pages/articles.js index efe2b76..1b3cd61 100644 --- a/src/pages/articles.js +++ b/src/pages/articles.js @@ -2,7 +2,12 @@ import React from "react" import { Main } from '../ui/ui' import { Grid, Row, Col } from 'react-flexbox-grid' import { graphql } from 'gatsby' +import { useFlexSearch } from 'react-use-flexsearch' +import { useLocation } from '@reach/router' +import * as queryString from 'query-string' +import toFrontmatter from '../helpers/toFrontmatter' import useCategoriesQuery from '../hooks/useCategoriesQuery' + import SEO from '../components/SEO/SEO' import Layout from '../components/Layout/Layout' import Articles from '../components/Articles/Articles' @@ -12,8 +17,20 @@ import Search from '../components/Articles/Search/Search' import Offers from '../components/Offers/Offers' import Newsletter from '../components/Newsletter/Newsletter' -const ArticlesPage = ({ data: { allMarkdownRemark: edges } }) => { +import SearchIcon from '../images/search-icon.svg' + +const ArticlesPage = ({ data: { allMarkdownRemark: edges, localSearchPages }, }) => { const { categories } = useCategoriesQuery() + const { search, pathname } = useLocation() + + const [query, setQuery] = React.useState(queryString.parse(search).search || '') + + const results = useFlexSearch( + query, + localSearchPages.index, + JSON.parse(localSearchPages.store) + ) + return ( @@ -25,16 +42,22 @@ const ArticlesPage = ({ data: { allMarkdownRemark: edges } }) => {

Статьи

- + {categories.map(({ node }, i) => )} - + +
+ {query ? results.length === 0 ?

  Ничего не найдено

: null : null} +
- + {query + ? results.length > 0 ? : null + : + } @@ -69,6 +92,10 @@ export const query = graphql` } } } + localSearchPages { + index + store + } } ` diff --git a/src/pages/index.js b/src/pages/index.js index 70823c3..3b4bc81 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -20,7 +20,7 @@ const IndexPage = ({ data: { allMarkdownRemark: edges } }) => {
-

Последние статьи

+

Последние статьи

смотреть все
diff --git a/src/templates/articleTemplate.js b/src/templates/articleTemplate.js index 8b19ec4..3c2d747 100644 --- a/src/templates/articleTemplate.js +++ b/src/templates/articleTemplate.js @@ -47,7 +47,7 @@ export const pageQuery = graphql` time tags description - date + date(formatString: "D MMMM YYYY", locale: "ru-RU") category } } diff --git a/src/templates/categoryTemplate.js b/src/templates/categoryTemplate.js index d78be08..826590e 100644 --- a/src/templates/categoryTemplate.js +++ b/src/templates/categoryTemplate.js @@ -7,7 +7,6 @@ import useCategoriesQuery from '../hooks/useCategoriesQuery' import SEO from '../components/SEO/SEO' import Layout from '../components/Layout/Layout' import Articles from '../components/Articles/Articles' -import Search from '../components/Articles/Search/Search' import Categories from '../components/Articles/Categories/Categories' import CategoryItem from '../components/Articles/Categories/CategoryItem' import Offers from '../components/Offers/Offers' @@ -26,12 +25,11 @@ const CategoryPage = ({ data: { allMarkdownRemark: edges }, pageContext: { title
-

Статьи

+

Категория: {title}

{categories.map(({ node }, i) => )} -

Показано {edges.edges.length} из категории "{title}"

diff --git a/src/templates/tagTemplate.js b/src/templates/tagTemplate.js index 501c3c6..fb1af3e 100644 --- a/src/templates/tagTemplate.js +++ b/src/templates/tagTemplate.js @@ -7,7 +7,6 @@ import useCategoriesQuery from '../hooks/useCategoriesQuery' import SEO from '../components/SEO/SEO' import Layout from '../components/Layout/Layout' import Articles from '../components/Articles/Articles' -import Search from '../components/Articles/Search/Search' import Categories from '../components/Articles/Categories/Categories' import CategoryItem from '../components/Articles/Categories/CategoryItem' import Offers from '../components/Offers/Offers' @@ -26,12 +25,11 @@ const TagPage = ({ data: { allMarkdownRemark: edges }, pageContext: { title } })
-

Статьи

+

Тэг: {title}

{categories.map(({ node }, i) => )} -

Показано {edges.edges.length} по тегу "{title}"