added tags, changed data structure, separated unsearchable posts from regular posts

This commit is contained in:
Kim, Jimin 2021-07-30 15:15:10 +09:00
parent 9922ff1fa4
commit 2d5582fda2
46 changed files with 463 additions and 342 deletions

4
.gitignore vendored
View file

@ -2,8 +2,8 @@
_/
# auto generated files
posts.json
posts/
source/src/data/map.json
source/src/data/content/
# dependencies
.pnp/

View file

@ -1,8 +1,9 @@
/**
* It reads markdown files and write its content and metadata to json files that can be imported by React.
* - Files and directories names starting with a underscore (_) will be ignored
* - Symbolic links are ignored as of the moment
* - Filename-to-url encoder is not perfect. Some non-url-friendly filenames might cause problems
* Read markdown files and write their content and metadata to json files which can then be imported by React.
* - File and directory names starting with an underscore (_) get ignored.
* - Symbolic links are not supported.
* - The Filename-to-URL encoder is not perfect. Some non-URL-friendly filenames might cause problems.
* - series must start with a number followed by an underscore
*/
import fs from "fs" // read and write files
@ -10,181 +11,302 @@ import path from "path" // get relative path
import matter from "gray-matter" // parse markdown metadata
import toc from "markdown-toc" // table of contents generation
const dirPath = "./markdown" // where it will look for markdown documents
const markdownPath = "./markdown" // where it will look for markdown documents
const outPath = "./src/data" // path to the json database
// data that will be converted to JSON string
const result = {
const contentDirectoryPath = `${outPath}/content`
const mapFilePath = `${outPath}/map.json`
interface Map {
// key: YYYYMMDD
// value: url
date: {
[key: string]: string[]
}
// key: tag name
// value: url
tags: {
[key: string]: string[]
}
// list of all meta data
meta: {
tags: string[]
}
// searchable, non-series posts
// must have a post date
// tag is not required
posts: {
[key: string]: {
title: string
date: string
tags: string[]
toc: string
preview: string
}
}
// urls of unsearchable posts
// it is here to quickly check if a post exists or not
unsearchable: {
[key: string]: {
title: string
}
}
}
// searchable data that will be converted to JSON string
const map: Map = {
date: {},
tags: {},
posts: {},
meta: {
tags: [],
},
unsearchable: {},
}
// creates directory/directories
// https://stackoverflow.com/a/40686946/12979111
function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
const sep = path.sep
const initDir = path.isAbsolute(targetDir) ? sep : ""
const baseDir = isRelativeToScript ? __dirname : "."
return targetDir.split(sep).reduce((parentDir, childDir) => {
const curDir = path.resolve(baseDir, parentDir, childDir)
try {
fs.mkdirSync(curDir)
} catch (err) {
if (err.code === "EEXIST") {
// curDir already exists!
return curDir
// converts file path to url
function path2URL(pathTpConvert: string): string {
return `/${path.relative(markdownPath, pathTpConvert)}`
.replace(/\.[^/.]+$/, "") // remove the file extension
.replace(/ /g, "-") // replace all space with a dash
}
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
if (err.code === "ENOENT") {
// Throw the original parentDir error on curDir `ENOENT` failure.
throw new Error(
`EACCES: permission denied, mkdir '${parentDir}'`
)
}
const caughtErr =
["EACCES", "EPERM", "EISDIR"].indexOf(err.code) > -1
if (
!caughtErr ||
(caughtErr && curDir === path.resolve(targetDir))
) {
throw err // Throw if it's just the last created dir.
}
}
return curDir
}, initDir)
}
// only supports folders and files (no symbolic links)
// does not scale well for large number of folders
// it calls itself for every directory it finds
function recursiveParser(fileOrFolderPath: string) {
// ignore if file/directory name starts with a underscore
// A recursive function that calls itself for every files and directories that it finds
function recursiveParsePosts(fileOrFolderPath: string) {
// get string after the last slash character
const fileOrFolderName = fileOrFolderPath.substring(
fileOrFolderPath.lastIndexOf("/") + 1
)
// ignore if file or directory name starts with a underscore
if (fileOrFolderName.startsWith("_")) return
// not perfect. Some filenames might cause problems.
const stats = fs.lstatSync(fileOrFolderPath) // checks if the path leads to a directory or a file
// get data about the given path
const stats = fs.lstatSync(fileOrFolderPath)
// don't use replaceAll
const urlPath = `/${path.relative(dirPath, fileOrFolderPath)}` // path that will be used as site url
.replace(/\.[^/.]+$/, "") // remove file extension
.replace(/ /g, "-") // replace space with a dash "-"
// if it's a directory, apply this function to every files/folders in it
// if it's a file, parse and save it to file
// if it's a directory, call this function to every files/directories in it
// if it's a file, parse it and then save it to file
if (stats.isDirectory()) {
fs.readdirSync(fileOrFolderPath).map((child) =>
recursiveParser(`${fileOrFolderPath}/${child}`)
)
fs.readdirSync(fileOrFolderPath).map((childPath) => {
recursiveParsePosts(`${fileOrFolderPath}/${childPath}`)
})
} else if (stats.isFile()) {
// skip if file is not a markdown file
// skip if it is not a markdown file
if (!fileOrFolderName.endsWith(".md")) {
console.log(`Ignoring non markdown file at: ${fileOrFolderPath}`)
return
}
const parsedMarkdown = matter(fs.readFileSync(fileOrFolderPath, "utf8")) // parse markdown metadata
const contentJSONFile = `${outPath}/posts${urlPath}.json`
// path that will be used as site url
const urlPath = path2URL(fileOrFolderPath)
mkDirByPathSync(
contentJSONFile.substring(0, contentJSONFile.lastIndexOf("/") + 1)
// parse markdown metadata
const parsedMarkdown = matter(fs.readFileSync(fileOrFolderPath, "utf8"))
if (!parsedMarkdown.data.title) {
throw Error(`Title is not defined in file: ${fileOrFolderPath}`)
}
if (!parsedMarkdown.data.date) {
throw Error(`Date is not defined in file: ${fileOrFolderPath}`)
}
// urlPath starts with a slash
const contentFilePath = `${contentDirectoryPath}${urlPath}.json`
// create directory to put json content files
fs.mkdirSync(
contentFilePath.substring(0, contentFilePath.lastIndexOf("/")),
{ recursive: true }
)
// write content to json file
fs.writeFileSync(
contentJSONFile,
contentFilePath,
JSON.stringify({
content: parsedMarkdown.content,
content: parsedMarkdown.content.trim(),
})
)
result.posts[urlPath] = parsedMarkdown.data
// Parse data that will be written to map.js
const postData = {
title: parsedMarkdown.data.title,
preview: "",
date: "",
tags: [],
toc: toc(parsedMarkdown.content).content,
}
// preview
// might cut mid html tag
result.posts[urlPath].preview =
parsedMarkdown.content.split(" ").slice(0, 20).join(" ") + " ..."
// content preview
// parsedMarkdown.excerpt is intentionally not used
// todo: fix potential improper closing of html tag
const slicedContent = parsedMarkdown.content.split(" ")
if (slicedContent.length > 19) {
postData.preview = slicedContent.slice(0, 19).join(" ") + " ..."
} else {
postData.preview = parsedMarkdown.content
}
// date
if (!result.posts[urlPath].date) {
throw Error(`Date does not exist in file: ${urlPath}`)
}
const postDate = new Date(parsedMarkdown.data.date)
result.posts[urlPath].date = postDate.toLocaleString("default", {
postData.date = postDate.toLocaleString("default", {
month: "short",
day: "numeric",
year: "numeric",
})
const YYYYMMDD = new Date(
postDate.getTime() - postDate.getTimezoneOffset() * 60 * 1000
)
.toISOString()
.split("T")[0]
if (result.date[YYYYMMDD]) result.date[YYYYMMDD].push(urlPath)
else result.date[YYYYMMDD] = [urlPath]
const YYYYMMDD = postDate.toISOString().split("T")[0]
if (map.date[YYYYMMDD]) {
map.date[YYYYMMDD].push(urlPath)
} else {
map.date[YYYYMMDD] = [urlPath]
}
//tags
if (result.posts[urlPath].tags) {
result.posts[urlPath].tags.forEach((tag) => {
if (result.tags[tag]) result.tags[tag].push(urlPath)
else result.tags[tag] = [urlPath]
postData.tags = parsedMarkdown.data.tags
if (postData.tags) {
postData.tags.forEach((tag) => {
if (map.tags[tag]) {
map.tags[tag].push(urlPath)
} else {
map.tags[tag] = [urlPath]
}
})
}
// toc
result.posts[urlPath].toc = toc(result.posts[urlPath].content).content
map.posts[urlPath] = postData
}
}
/** Step 1
* Deleting existing files
*/
function recursiveParseUnsearchable(fileOrFolderPath: string) {
// get string after the last slash character
const fileOrFolderName = fileOrFolderPath.substring(
fileOrFolderPath.lastIndexOf("/") + 1
)
// ignore if file or directory name starts with a underscore
if (fileOrFolderName.startsWith("_")) return
// illegal names
if (
fileOrFolderPath == "./markdown/unsearchable/posts" ||
fileOrFolderPath == "./markdown/unsearchable/series"
)
throw Error(
`Illegal name (posts/series) in path: "${fileOrFolderPath}".`
)
// get data about the given path
const stats = fs.lstatSync(fileOrFolderPath)
// if it's a directory, call this function to every files/directories in it
// if it's a file, parse it and then save it to file
if (stats.isDirectory()) {
fs.readdirSync(fileOrFolderPath).map((childPath) => {
recursiveParseUnsearchable(`${fileOrFolderPath}/${childPath}`)
})
} else if (stats.isFile()) {
// skip if it is not a markdown file
if (!fileOrFolderName.endsWith(".md")) {
console.log(`Ignoring non markdown file at: ${fileOrFolderPath}`)
return
}
const urlPath = path2URL(fileOrFolderPath)
// parse markdown metadata
const parsedMarkdown = matter(fs.readFileSync(fileOrFolderPath, "utf8"))
if (!parsedMarkdown.data.title) {
throw Error(`Title is not defined in file: ${fileOrFolderPath}`)
}
// urlPath starts with a slash
const contentFilePath = `${contentDirectoryPath}${urlPath}.json`
// create directory to put json content files
fs.mkdirSync(
contentFilePath.substring(0, contentFilePath.lastIndexOf("/")),
{ recursive: true }
)
// write content to json file
fs.writeFileSync(
contentFilePath,
JSON.stringify({
content: parsedMarkdown.content.trim(),
})
)
// Parse data that will be written to map.js
map.unsearchable[
urlPath.substring(
urlPath
.substring(1) // ignore the first slash
.indexOf("/") + 1
)
] = {
title: parsedMarkdown.data.title,
}
}
}
// function recursiveParseSeries(filOrFolderPath: string) {
// console.log(filOrFolderPath)
// }
// Delete existing files
try {
fs.rmSync(`${outPath}/posts`, { recursive: true })
fs.rmSync(contentDirectoryPath, { recursive: true })
// eslint-disable-next-line no-empty
} catch (err) {}
try {
fs.unlinkSync(`${outPath}/posts.json`)
fs.unlinkSync(mapFilePath)
// eslint-disable-next-line no-empty
} catch (err) {}
/** Step 2
* Populate result and write to src/data/posts/
*/
// check if it's a directory and start recursive parse function
if (!fs.lstatSync(markdownPath).isDirectory())
throw Error("Invalid markdown path")
// check if it's a directory and start recursive function
if (fs.lstatSync(dirPath).isDirectory()) {
recursiveParser(dirPath)
} else {
throw Error("Initial path given does not lead to a directory")
}
if (!fs.lstatSync(markdownPath + "/posts").isDirectory())
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
if (!fs.lstatSync(markdownPath + "/unsearchable").isDirectory())
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
// if (!fs.lstatSync(markdownPath + "/series").isDirectory())
// throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
recursiveParsePosts(markdownPath + "/posts")
recursiveParseUnsearchable(markdownPath + "/unsearchable")
// recursiveParseSeries(markdownPath + "/series")
// sort dates
let dateKeys: string[] = []
for (const dateKey in result.date) {
for (const dateKey in map.date) {
dateKeys.push(dateKey)
}
dateKeys = dateKeys.sort()
const resultDate = result.date
result.date = {}
const TmpDate = map.date
map.date = {}
dateKeys.forEach(
(sortedDateKey) => (result.date[sortedDateKey] = resultDate[sortedDateKey])
)
dateKeys.forEach((sortedDateKey) => {
map.date[sortedDateKey] = TmpDate[sortedDateKey]
})
/** Step 3
* write to src/data/posts.json
*/
// fill meta data
for (const tag in map.tags) {
map.meta.tags.push(tag)
}
fs.writeFileSync(`${outPath}/posts.json`, JSON.stringify(result) + "\n")
// write to src/data/map.json
fs.writeFileSync(mapFilePath, JSON.stringify(map))

View file

@ -1,15 +0,0 @@
---
title: About
date: 2021-04-20
---
About page
- about me
- email: developomp@gmail.com
- discord: developomp#0001 (yes I have nitro)
- [github profile](https://github.com/developomp)
- me as a person
- [goals](/goals)
- [Portfolio](/portfolio)

View file

@ -1,8 +0,0 @@
---
title: games
date: 2020-08-16
---
- [TicTacToe](/games/tictactoe)
- [pong](/games/pong)
- [click](/games/click)

View file

@ -1,16 +0,0 @@
---
title: goals
date: 2021-05-11
---
- skill
- type 400 letters per minute (both english and korean)
- milestone
- make a high quality video with at least 1M views on YouTube
- 1000 star on a gh repository
- completely switch to RISC based CPU powered laptop
- project
- create fully functional discord clone from scratch
- make a multiplayer game that can pay for itself
- assemble my own linux distro
- assemble my own mechanical keyboard

View file

@ -0,0 +1,9 @@
---
title: Test post
date: 2021-07-26
tags:
- tag1
- tag2
---
A post have title, date, tag, and content.

View file

@ -1,12 +0,0 @@
---
title: Quote NO.3
date: 2021-03-18
tags:
- quotes
---
In the introduction of one of his book: "The Future of the mind" (9th paragraph)
> "To fathom the greatest secrets in the universe, one did not need telepathic or superhuman abilities. One just had to have a open, determined, and and curious mind."
<div style="text-align: right"> <i>- Michio Kaku (2014)</i> </div>

View file

@ -0,0 +1,3 @@
---
title: quotes
---

View file

@ -1,10 +1,8 @@
---
title: Quote NO.1
date: 2020-08-16
tags:
- quotes
---
> "Get the fuck out of my lawn"
> "Get out of my lawn!!"
<div style="text-align: right"> <i>- Mahatma Ghanghi (1885)</i> </div>

View file

@ -1,13 +1,11 @@
---
title: Quote NO.2
date: 2021-02-20
tags:
- quotes
---
In a Q&A session in Aalto Talk with Linus Torvalds, hosted by Aalto Center for Entrepreneurship (ACE) in Otaniemi.
> "Nvidia, Fuck you!"
> "Nvidia, FU€\* you!"
<div style="padding: 56.25% 0px 0px; position: relative;"><iframe src="https://www.youtube.com/embed/MShbP3OpASA?cc_load_policy=1&end=3005&iv_load_policy=3&rel=0&start=2993" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen scrolling="no" style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%;"></iframe></div>
<br>

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.10
date: 2021-03-22
tags:
- quotes
---
> Don't forget what you planned to be

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.11
date: 2021-03-22
tags:
- quotes
---
> Yesterday is a lecture for today

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.12
date: 2021-03-22
tags:
- quotes
---
> Practice isn't a action. Its a formation.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.13
date: 2021-03-22
tags:
- quotes
---
> Don't forget the peaks and the valleys of your life.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.14
date: 2021-03-22
tags:
- quotes
---
> Those who see only the present lose their future.<br />

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.15
date: 2021-03-22
tags:
- quotes
---
> The depth of a proverb is proportional to the depth of the reader's thoughts.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.16
date: 2021-03-22
tags:
- quotes
---
> Words of wisdom deepens the more you think about it.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.17
date: 2021-03-22
tags:
- quotes
---
> God didn't bless us with the best, so let's do it ourself.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.18
date: 2021-03-22
tags:
- quotes
---
> I got a purpose now, so why sit down?<br>

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.19
date: 2021-03-22
tags:
- quotes
---
> Finding the problem is the first step to solving anything

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.1
date: 2021-03-22
tags:
- quotes
---
> Let's find problems in ourselves first

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.20
date: 2021-03-22
tags:
- quotes
---
> Look at the clock and wait for the next minute to come.<br>

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.21
date: 2021-03-22
tags:
- quotes
---
> Kill an ant. Throw it and try to find it.<br>

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.22
date: 2021-03-22
tags:
- quotes
---
> Lot of things learned, nothing useful.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.23
date: 2021-03-22
tags:
- quotes
---
> To give 10, one should know a 100.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.24
date: 2021-03-22
tags:
- quotes
---
> Think about everything

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.25
date: 2021-03-22
tags:
- quotes
---
> Challenge yourself to give your best at all time.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.26
date: 2021-03-22
tags:
- quotes
---
> Escape from the valleys of life doesn't happen in an instant.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.27
date: 2021-03-22
tags:
- quotes
---
> Sometimes I am amazed by the fact that I am aware of anything.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.28
date: 2021-03-22
tags:
- quotes
---
> Mind is like a sword. It will be dull if you stop sharpening it.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.29
date: 2021-03-22
tags:
- quotes
---
> Even if the day comes when we can live for hundreds of years, we'll still make a world where hard working is a necessity.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.2
date: 2021-03-22
tags:
- quotes
---
> Don't be great for your fame, but be famous for your greatness.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.30
date: 2021-03-22
tags:
- quotes
---
> If you think too much about the answer, you'll forget what the question was.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.31
date: 2021-03-22
tags:
- quotes
---
> People earns highest respect from me are those who appreciate critiques.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.32
date: 2021-05-10
tags:
- quotes
---
> Any field is fascinating as long as there are no exams.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.3
date: 2021-03-22
tags:
- quotes
---
> If you have a proverbs, record it. Treat it as if it's a jewel. In the future, this fine gem will be the eyes of many, and a lamp to light the ways of people.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.4
date: 2021-03-22
tags:
- quotes
---
> I don't want to call it learning that I didn't learn with my heart when I learn.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.5
date: 2021-03-22
tags:
- quotes
---
> Don't define anything different from normality as a failure.

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.6
date: 2021-03-22
tags:
- quotes
---
> What did you do when everyone in the world ran?

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.7
date: 2021-03-22
tags:
- quotes
---
> The 1000 miles you've walked so far are not important. What's important is

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.8
date: 2021-03-22
tags:
- quotes
---
> Out of all the thing you've done and haven't done, which one do you regret more?

View file

@ -1,8 +1,6 @@
---
title: My Quote NO.9
date: 2021-03-22
tags:
- quotes
---
> People who don't know what they're talking about are the poorest people in the world.

View file

@ -0,0 +1,31 @@
import React from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import styled from "styled-components"
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;
border-radius: 10px;
background-color: ${theming.color.linkColor};
color: white;
`
interface TagProps {
text: string
}
export default class Tag extends React.Component<TagProps> {
render() {
return (
<StyledTag>
<FontAwesomeIcon icon={faTag} /> &nbsp;{this.props.text}
</StyledTag>
)
}
}

View file

@ -1,17 +1,24 @@
import React from "react"
import marked from "marked"
import { Helmet } from "react-helmet-async"
import styled from "styled-components"
import posts from "../data/posts.json"
import posts from "../data/map.json"
import Tag from "../components/Tag"
import NotFound from "./NotFound"
import Spinner from "../components/Spinner"
const StyledTitle = styled.h1`
margin-bottom: 1rem;
`
interface PageProps {}
interface PageState {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fetchedPage: any
isUnsearchable: boolean
loading: boolean
}
@ -19,28 +26,48 @@ export default class Page extends React.Component<PageProps, PageState> {
constructor(props) {
super(props)
this.state = {
isUnsearchable: false,
fetchedPage: undefined,
loading: true,
}
}
async componentDidMount() {
const url = location.pathname.replace(/\/$/, "")
const fetchedPage = posts.posts[url] // remove a trailing slash
const url = location.pathname.replace(/\/$/, "") // remove trailing slash
let _isUnsearchable = false
// fetch page
let fetchedPage = posts.posts[url]
if (!fetchedPage) {
fetchedPage = posts.unsearchable[url]
_isUnsearchable = true
this.setState({ isUnsearchable: true })
if (!fetchedPage) {
this.setState({
loading: false,
})
return
}
}
const fetched_content = (await import(`../data/posts${url}.json`))
let fetched_content
if (_isUnsearchable) {
fetched_content = (
await import(`../data/content/unsearchable${url}.json`)
).content
} else {
fetched_content = (await import(`../data/content${url}.json`))
.content
}
fetchedPage.content = fetched_content ? fetched_content : "No content"
fetchedPage.toc = fetchedPage?.toc ? fetchedPage.toc : undefined
fetchedPage.title = fetchedPage?.title ? fetchedPage.title : "No title"
fetchedPage.date = fetchedPage?.date ? fetchedPage.date : "Unknown date"
if (!_isUnsearchable) {
fetchedPage.date = fetchedPage?.date
? fetchedPage.date
: "Unknown date"
}
this.setState({
fetchedPage: fetchedPage,
@ -69,12 +96,39 @@ export default class Page extends React.Component<PageProps, PageState> {
content={`${process.env.PUBLIC_URL}/icon/icon.svg`}
/>
</Helmet>
<div className="card main-content">
<h1>{this.state.fetchedPage.title}</h1>
<StyledTitle>
{this.state.fetchedPage.title}
</StyledTitle>
{/* Post tags */}
<small>
Published on {this.state.fetchedPage.date} by
developomp
<table>
{this.state.fetchedPage.tags ? (
this.state.fetchedPage.tags.map((tag) => {
return (
<td
key={
this.state.fetchedPage
.title + tag
}
>
<Tag text={tag} />
</td>
)
})
) : (
<></>
)}
</table>
{this.state.isUnsearchable ? (
<></>
) : (
<>Published on {this.state.fetchedPage.date}</>
)}
</small>
{/* Horizontal Separator */}
<hr />
{
this.state.fetchedPage.toc && (

View file

@ -9,7 +9,8 @@ import marked from "marked"
import { Helmet } from "react-helmet-async"
import theming from "../theming"
import posts from "../data/posts.json"
import posts from "../data/map.json"
import Tag from "../components/Tag"
const StyledPostList = styled.div`
padding-top: 2rem;
@ -31,6 +32,7 @@ const StyledH1 = styled.h1`
const StyledTitle = styled.h1`
font-size: 2rem;
font-style: bold;
margin-bottom: 1rem;
`
const StyledLink = styled(Link)`
@ -51,7 +53,7 @@ const StyledPostCard = styled.div`
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
text-align: left;
margin-bottom: 20px;
padding: 10px 20px;
padding: 1rem 2rem 2rem 2rem;
`
interface PostListProps {
@ -75,7 +77,7 @@ export default class PostList extends React.Component<
const howMany = props.howMany | 0
const isLimited = howMany ? true : false
const h1Text = isLimited ? `${howMany} recent posts` : "All posts"
const h1Text = isLimited ? `recent posts` : "All posts"
this.state = {
howMany: howMany,
@ -109,12 +111,22 @@ export default class PostList extends React.Component<
<StyledPostCard key={url} className="card main-content">
<StyledTitle>
<StyledLink to={`${process.env.PUBLIC_URL}${url}`}>
{post?.title ? post.title : "Unknown title"}
{post?.title ? post.title : "No title"}
</StyledLink>
</StyledTitle>
<small>
<table>
{post.tags.map((tag) => {
return (
<td key={post.title + tag}>
<Tag text={tag} />
</td>
)
})}
</table>
Published on {post?.date ? post.date : "Unknown date"}
</small>
<hr />
<div
className="link-color"
@ -124,7 +136,7 @@ export default class PostList extends React.Component<
></div>
<small>
<StyledLink to={`${process.env.PUBLIC_URL}${url}`}>
Read more
<u>Read more</u>
</StyledLink>
</small>
</StyledPostCard>

View file

@ -1,6 +1,6 @@
import React from "react"
import { useState } from "react"
import styled from "styled-components"
import { Link } from "react-router-dom"
import { Link, BrowserRouter, useLocation } from "react-router-dom"
import { Helmet } from "react-helmet-async"
import { DateRange } from "react-date-range"
import queryString from "query-string"
@ -9,7 +9,8 @@ import "react-date-range/dist/styles.css"
import "react-date-range/dist/theme/default.css"
import theming from "../theming"
import pages from "../data/posts.json"
import map from "../data/map.json"
import Tag from "../components/Tag"
const StyledSearch = styled.div`
margin: auto;
@ -22,46 +23,33 @@ const StyledSearch = styled.div`
})};
`
interface SearchProps {}
const StyledTagTable = styled.table`
margin-left: auto;
margin-right: auto;
`
interface SearchState {
tags: string[]
dateRange: unknown[]
query: {
from?: string // YYYYMMDD
to?: string // YYYYMMDD
tags?: string[] // ["include", "!doNotInclude"]
}
export default function Search() {
return (
<BrowserRouter>
<_Search />
</BrowserRouter>
)
}
export default class Search extends React.Component<SearchProps, SearchState> {
constructor(props) {
super(props)
const tags: string[] = []
for (const tag in pages.tags) {
tags.push(tag)
}
const parsedQuery = queryString.parse(location.search)
function _Search() {
const parsedQuery = queryString.parse(useLocation().search)
parsedQuery.tags = parsedQuery.tags
? (parsedQuery.tags as string).split(",")
: []
this.state = {
tags: tags,
dateRange: [
const [dateRange, setDateRange] = useState([
{
startDate: new Date(),
endDate: null,
key: "selection",
},
],
query: parsedQuery,
}
}
])
render() {
return (
<>
<Helmet>
@ -69,13 +57,10 @@ export default class Search extends React.Component<SearchProps, SearchState> {
<meta property="og:title" content="Search" />
<meta property="og:type" content="website" />
<meta
property="og:url"
content={`${process.env.PUBLIC_URL}`}
/>
<meta property="og:url" content={process.env.PUBLIC_URL} />
<meta
property="og:image"
content={`${process.env.PUBLIC_URL}/icon/icon.svg`}
content={process.env.PUBLIC_URL + "/icon/icon.svg"}
/>
<meta property="og:description" content="search" />
</Helmet>
@ -85,26 +70,50 @@ export default class Search extends React.Component<SearchProps, SearchState> {
editableDateInputs={true}
moveRangeOnFirstSelection={false}
retainEndDateOnFirstSelection={true}
ranges={this.state.dateRange}
ranges={dateRange}
onChange={(item) => {
this.setState({ dateRange: [item.selection] })
setDateRange([item.selection])
}}
/>
<br />
available tags: {this.state.tags}
available tags:
<small>
<StyledTagTable>
{map.meta.tags.map((tag) => {
return (
<td key={tag}>
<Tag text={tag} />
</td>
)
})}
</StyledTagTable>
</small>
<br />
<br />
selected tags: {this.state.query.tags?.join(", ")}
Selected tags:
<small>
<StyledTagTable>
{parsedQuery.tags?.map((tag) => {
return (
<td key={tag}>
<Tag text={tag} />
</td>
)
})}
</StyledTagTable>
</small>
<br />
date from: {this.state.query.from}
date from: {parsedQuery.from}
<br />
date to: {this.state.query.to}
date to: {parsedQuery.to}
<br />
<Link to="/search?&from=YYYYMMDD&to=TTTTMMDD&tags=include,!exclude">
Search
<Link to="/search?&from=YYYYMMDD&to=YYYYMMDD&tags=include,!exclude">
Search1
</Link>
<Link to="/search?&from=YYYYMMDD&to=YYYYMMDD&tags=include2,!exclude2">
Search2
</Link>
</StyledSearch>
</>
)
}
}