diff --git a/.gitignore b/.gitignore index 800720e..ace439d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ _/ # auto generated files source/src/data/map.json +source/src/data/search.json source/src/data/content/ # dependencies diff --git a/README.md b/README.md index fbf3792..b6e638c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Tools / Frameworks / Packages used: | [gray-matter](https://github.com/jonschlinkert/gray-matter) | Parsing markdown files | | [react-tooltip](https://github.com/wwayne/react-tooltip) | Tooltips | | [react-date-range](https://github.com/hypeserver/react-date-range) | Date picker for search page | +| [elasticlunr](https://github.com/weixsong/elasticlunr.js) | Search engine | # Setup diff --git a/source/generate.ts b/source/generate.ts index 99b1d93..540856a 100644 --- a/source/generate.ts +++ b/source/generate.ts @@ -8,6 +8,7 @@ import fs from "fs" // read and write files import path from "path" // get relative path +import elasticlunr from "elasticlunr" // search index generation import matter from "gray-matter" // parse markdown metadata import toc from "markdown-toc" // table of contents generation @@ -68,6 +69,14 @@ interface Map { } } +interface SeriesMap { + // key: url + [key: string]: { + index: number + url: string + }[] +} + // searchable data that will be converted to JSON string const map: Map = { date: {}, @@ -79,16 +88,12 @@ const map: Map = { series: {}, unsearchable: {}, } - -interface SeriesMap { - // key: url - [key: string]: { - index: number - url: string - }[] -} - const seriesMap: SeriesMap = {} +const index = elasticlunr(function () { + this.addField("title" as never) + this.addField("body" as never) + this.setRef("url" as never) +}) // converts file path to url function path2URL(pathToConvert: string): string { @@ -209,6 +214,11 @@ function recursiveParsePosts(fileOrFolderPath: string) { } map.posts[urlPath] = postData + index.addDoc({ + title: parsedMarkdown.data.title, + body: parsedMarkdown.content, + url: urlPath, + }) } } @@ -244,7 +254,12 @@ function recursiveParseUnsearchable(fileOrFolderPath: string) { return } - const urlPath = path2URL(fileOrFolderPath) + const _urlPath = path2URL(fileOrFolderPath) + const urlPath = _urlPath.slice( + _urlPath + .slice(1) // ignore the first slash + .indexOf("/") + 1 + ) // parse markdown metadata const parsedMarkdown = matter(fs.readFileSync(fileOrFolderPath, "utf8")) @@ -254,7 +269,7 @@ function recursiveParseUnsearchable(fileOrFolderPath: string) { } // urlPath starts with a slash - const contentFilePath = `${contentDirectoryPath}${urlPath}.json` + const contentFilePath = `${contentDirectoryPath}/unsearchable${urlPath}.json` // create directory to put json content files fs.mkdirSync( @@ -271,15 +286,15 @@ function recursiveParseUnsearchable(fileOrFolderPath: string) { ) // Parse data that will be written to map.js - map.unsearchable[ - urlPath.slice( - urlPath - .slice(1) // ignore the first slash - .indexOf("/") + 1 - ) - ] = { + map.unsearchable[urlPath] = { title: parsedMarkdown.data.title, } + + index.addDoc({ + title: parsedMarkdown.data.title, + body: parsedMarkdown.content, + url: urlPath, + }) } } @@ -395,6 +410,11 @@ function recursiveParseSeries(fileOrFolderPath: string) { map.series[urlPath] = { ...postData, order: [], length: 0 } } else { map.posts[urlPath] = postData + index.addDoc({ + title: parsedMarkdown.data.title, + body: parsedMarkdown.content, + url: urlPath, + }) for (const key of Object.keys(map.series)) { if (urlPath.slice(0, urlPath.lastIndexOf("/")).includes(key)) { const index = parseInt( @@ -494,5 +514,5 @@ for (const seriesURL in seriesMap) { map.series[seriesURL].order = seriesMap[seriesURL].map((item) => item.url) } -// write to src/data/map.json fs.writeFileSync(mapFilePath, JSON.stringify(map)) +fs.writeFileSync(outPath + "/search.json", JSON.stringify(index)) diff --git a/source/package.json b/source/package.json index 7f66a7c..ac959fd 100644 --- a/source/package.json +++ b/source/package.json @@ -21,7 +21,9 @@ "@fortawesome/free-regular-svg-icons": "^5.15.3", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/react-fontawesome": "^0.1.14", + "@types/elasticlunr": "^0.9.2", "date-fns": "^2.23.0", + "elasticlunr": "^0.9.5", "gray-matter": "^4.0.3", "local-storage-fallback": "^4.1.2", "markdown-toc": "^1.2.0", diff --git a/source/src/components/PostCard.tsx b/source/src/components/PostCard.tsx index ea02044..8d23902 100644 --- a/source/src/components/PostCard.tsx +++ b/source/src/components/PostCard.tsx @@ -4,7 +4,9 @@ import styled from "styled-components" import { Link } from "react-router-dom" import theming from "../theming" + import Tag from "../components/Tag" +import TagList from "../components/TagList" const StyledTitle = styled.h1` font-size: 2rem; @@ -68,19 +70,20 @@ export default class PostCard extends React.Component { - + {this.props.postData.tags ? ( this.props.postData.tags.map((tag) => { return ( - + ) }) ) : ( <> )} -
- -
+ Published on{" "} {this.props.postData?.date ? this.props.postData.date diff --git a/source/src/components/Tag.tsx b/source/src/components/Tag.tsx index 2e8bc3f..369f5a4 100644 --- a/source/src/components/Tag.tsx +++ b/source/src/components/Tag.tsx @@ -6,7 +6,6 @@ import theming from "../theming" import { faTag } from "@fortawesome/free-solid-svg-icons" const StyledTag = styled.div` - display: table; text-align: center; padding: 0 0.8rem 0.1rem 0.8rem; diff --git a/source/src/components/TagList.tsx b/source/src/components/TagList.tsx new file mode 100644 index 0000000..e38614e --- /dev/null +++ b/source/src/components/TagList.tsx @@ -0,0 +1,18 @@ +import React from "react" +import styled from "styled-components" + +const StyledTagList = styled.div` + display: flex; + flex-wrap: wrap; + row-gap: 0.5rem; + column-gap: 0.5rem; + flex-direction: row; + justify-content: center; +` + +export default class TagList extends React.Component { + render() { + // eslint-disable-next-line react/prop-types + return {this.props.children} + } +} diff --git a/source/src/pages/Page.tsx b/source/src/pages/Page.tsx index e294f50..172b7b0 100644 --- a/source/src/pages/Page.tsx +++ b/source/src/pages/Page.tsx @@ -4,13 +4,14 @@ import { Helmet } from "react-helmet-async" import { Link } from "react-router-dom" import styled from "styled-components" -import map from "../data/map.json" +import theming from "../theming" import Tag from "../components/Tag" +import TagList from "../components/TagList" import NotFound from "./NotFound" import Spinner from "../components/Spinner" -import theming from "../theming" +import map from "../data/map.json" const StyledTitle = styled.h1` margin-bottom: 1rem; @@ -224,7 +225,7 @@ export default class Page extends React.Component { {/* Post tags */} - + {this.state.fetchedPage.tags ? ( this.state.fetchedPage.tags.map((tag) => { return ( @@ -241,7 +242,7 @@ export default class Page extends React.Component { ) : ( <> )} -
+ {this.state.isUnsearchable ? ( <> ) : ( diff --git a/source/src/pages/Search.tsx b/source/src/pages/Search.tsx index 06d7c15..293e72e 100644 --- a/source/src/pages/Search.tsx +++ b/source/src/pages/Search.tsx @@ -1,16 +1,21 @@ -import { useState } from "react" +import { useEffect, useState } from "react" import styled from "styled-components" -import { BrowserRouter, useLocation, useHistory } from "react-router-dom" +import { useLocation, useHistory } from "react-router-dom" import { Helmet } from "react-helmet-async" import { DateRange } from "react-date-range" -import queryString from "query-string" +import queryString from "query-string" // parsing url query +import elasticlunr from "elasticlunr" // search engine + +import map from "../data/map.json" +import searchIndex from "../data/search.json" +import theming from "../theming" import "react-date-range/dist/styles.css" import "react-date-range/dist/theme/default.css" -import theming from "../theming" -import map from "../data/map.json" import Tag from "../components/Tag" +import TagList from "../components/TagList" +import PostCard from "../components/PostCard" const StyledSearch = styled.div` text-align: center; @@ -39,23 +44,18 @@ const StyledSearchControlContainer = styled.div` } ` -const StyledSearchResult = styled.div`` - -const StyledTagTable = styled.table` - margin: 0 auto 0 auto; -` - +// todo: find ways to get rid of wrapper component export default function Search() { - return ( - - <_Search /> - - ) + return <_Search /> } // have to be in a separate component for tags to update when the urls change -// todo: check if using keys will fix the issue +// todo: check if using keys will allow me to use class components function _Search() { + const [index, setIndex] = useState({} as elasticlunr.Index) + + useEffect(() => setIndex(elasticlunr.Index.load(searchIndex as never)), []) + const _history = useHistory() const _location = useLocation() @@ -76,6 +76,10 @@ function _Search() { }, ]) + const [postCards, setPostCards] = useState([]) + + const [searchInput, setSearchInput] = useState("") + return ( <> @@ -128,19 +132,20 @@ function _Search() { /> - + + setSearchInput(event.target.value) + } + />

- + {query.tags?.map((tag) => { - return ( - - - - ) + return })} - +
date from: {query.from} @@ -176,10 +181,38 @@ function _Search() { > Search test 2 +
+
- {map.meta.tags} + {postCards} ) } diff --git a/source/yarn.lock b/source/yarn.lock index 7e659b5..7096af2 100644 --- a/source/yarn.lock +++ b/source/yarn.lock @@ -1795,6 +1795,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/elasticlunr@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@types/elasticlunr/-/elasticlunr-0.9.2.tgz#f8ec69ff40c4538289fc4b2e0ab167bcd927265f" + integrity sha512-GAW5518ySodReGGelTacR+ei7clSN8T9+d2UjcBjGhhIcd7bMRMfalHCcKkNLFMjoqnNFmBKWPOP5w+BT27K1A== + "@types/eslint@^7.2.6": version "7.2.10" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.10.tgz#4b7a9368d46c0f8cd5408c23288a59aa2394d917" @@ -4512,6 +4517,11 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== +elasticlunr@^0.9.5: + version "0.9.5" + resolved "https://registry.yarnpkg.com/elasticlunr/-/elasticlunr-0.9.5.tgz#65541bb309dddd0cf94f2d1c8861b2be651bb0d5" + integrity sha1-ZVQbswnd3Qz5Ty0ciGGyvmUbsNU= + electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.723: version "1.3.727" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf"