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 # auto generated files
posts.json source/src/data/map.json
posts/ source/src/data/content/
# dependencies # dependencies
.pnp/ .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. * Read markdown files and write their content and metadata to json files which can then be imported by React.
* - Files and directories names starting with a underscore (_) will be ignored * - File and directory names starting with an underscore (_) get ignored.
* - Symbolic links are ignored as of the moment * - Symbolic links are not supported.
* - Filename-to-url encoder is not perfect. Some non-url-friendly filenames might cause problems * - 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 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 matter from "gray-matter" // parse markdown metadata
import toc from "markdown-toc" // table of contents generation 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 const outPath = "./src/data" // path to the json database
// data that will be converted to JSON string const contentDirectoryPath = `${outPath}/content`
const result = { 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: {}, date: {},
tags: {}, tags: {},
posts: {}, posts: {},
meta: {
tags: [],
},
unsearchable: {},
} }
// creates directory/directories // converts file path to url
// https://stackoverflow.com/a/40686946/12979111 function path2URL(pathTpConvert: string): string {
function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { return `/${path.relative(markdownPath, pathTpConvert)}`
const sep = path.sep .replace(/\.[^/.]+$/, "") // remove the file extension
const initDir = path.isAbsolute(targetDir) ? sep : "" .replace(/ /g, "-") // replace all space with a dash
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
}
// 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) // A recursive function that calls itself for every files and directories that it finds
// does not scale well for large number of folders function recursiveParsePosts(fileOrFolderPath: string) {
// it calls itself for every directory it finds // get string after the last slash character
function recursiveParser(fileOrFolderPath: string) {
// ignore if file/directory name starts with a underscore
const fileOrFolderName = fileOrFolderPath.substring( const fileOrFolderName = fileOrFolderPath.substring(
fileOrFolderPath.lastIndexOf("/") + 1 fileOrFolderPath.lastIndexOf("/") + 1
) )
// ignore if file or directory name starts with a underscore
if (fileOrFolderName.startsWith("_")) return if (fileOrFolderName.startsWith("_")) return
// not perfect. Some filenames might cause problems. // get data about the given path
const stats = fs.lstatSync(fileOrFolderPath) // checks if the path leads to a directory or a file const stats = fs.lstatSync(fileOrFolderPath)
// don't use replaceAll // if it's a directory, call this function to every files/directories in it
const urlPath = `/${path.relative(dirPath, fileOrFolderPath)}` // path that will be used as site url // if it's a file, parse it and then save it to file
.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 (stats.isDirectory()) { if (stats.isDirectory()) {
fs.readdirSync(fileOrFolderPath).map((child) => fs.readdirSync(fileOrFolderPath).map((childPath) => {
recursiveParser(`${fileOrFolderPath}/${child}`) recursiveParsePosts(`${fileOrFolderPath}/${childPath}`)
) })
} else if (stats.isFile()) { } else if (stats.isFile()) {
// skip if file is not a markdown file // skip if it is not a markdown file
if (!fileOrFolderName.endsWith(".md")) { if (!fileOrFolderName.endsWith(".md")) {
console.log(`Ignoring non markdown file at: ${fileOrFolderPath}`) console.log(`Ignoring non markdown file at: ${fileOrFolderPath}`)
return return
} }
const parsedMarkdown = matter(fs.readFileSync(fileOrFolderPath, "utf8")) // parse markdown metadata // path that will be used as site url
const contentJSONFile = `${outPath}/posts${urlPath}.json` const urlPath = path2URL(fileOrFolderPath)
mkDirByPathSync( // parse markdown metadata
contentJSONFile.substring(0, contentJSONFile.lastIndexOf("/") + 1) 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 // write content to json file
fs.writeFileSync( fs.writeFileSync(
contentJSONFile, contentFilePath,
JSON.stringify({ 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 // content preview
// might cut mid html tag // parsedMarkdown.excerpt is intentionally not used
result.posts[urlPath].preview = // todo: fix potential improper closing of html tag
parsedMarkdown.content.split(" ").slice(0, 20).join(" ") + " ..." const slicedContent = parsedMarkdown.content.split(" ")
if (slicedContent.length > 19) {
postData.preview = slicedContent.slice(0, 19).join(" ") + " ..."
} else {
postData.preview = parsedMarkdown.content
}
// date // date
if (!result.posts[urlPath].date) {
throw Error(`Date does not exist in file: ${urlPath}`)
}
const postDate = new Date(parsedMarkdown.data.date) const postDate = new Date(parsedMarkdown.data.date)
result.posts[urlPath].date = postDate.toLocaleString("default", { postData.date = postDate.toLocaleString("default", {
month: "short", month: "short",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
}) })
const YYYYMMDD = new Date( const YYYYMMDD = postDate.toISOString().split("T")[0]
postDate.getTime() - postDate.getTimezoneOffset() * 60 * 1000 if (map.date[YYYYMMDD]) {
) map.date[YYYYMMDD].push(urlPath)
.toISOString() } else {
.split("T")[0] map.date[YYYYMMDD] = [urlPath]
}
if (result.date[YYYYMMDD]) result.date[YYYYMMDD].push(urlPath)
else result.date[YYYYMMDD] = [urlPath]
//tags //tags
if (result.posts[urlPath].tags) { postData.tags = parsedMarkdown.data.tags
result.posts[urlPath].tags.forEach((tag) => { if (postData.tags) {
if (result.tags[tag]) result.tags[tag].push(urlPath) postData.tags.forEach((tag) => {
else result.tags[tag] = [urlPath] if (map.tags[tag]) {
map.tags[tag].push(urlPath)
} else {
map.tags[tag] = [urlPath]
}
}) })
} }
// toc map.posts[urlPath] = postData
result.posts[urlPath].toc = toc(result.posts[urlPath].content).content
} }
} }
/** Step 1 function recursiveParseUnsearchable(fileOrFolderPath: string) {
* Deleting existing files // get string after the last slash character
*/ const fileOrFolderName = fileOrFolderPath.substring(
try { fileOrFolderPath.lastIndexOf("/") + 1
fs.rmSync(`${outPath}/posts`, { recursive: true }) )
// eslint-disable-next-line no-empty
} catch (err) {}
try { // ignore if file or directory name starts with a underscore
fs.unlinkSync(`${outPath}/posts.json`) if (fileOrFolderName.startsWith("_")) return
// eslint-disable-next-line no-empty
} catch (err) {}
/** Step 2 // illegal names
* Populate result and write to src/data/posts/ if (
*/ fileOrFolderPath == "./markdown/unsearchable/posts" ||
fileOrFolderPath == "./markdown/unsearchable/series"
)
throw Error(
`Illegal name (posts/series) in path: "${fileOrFolderPath}".`
)
// check if it's a directory and start recursive function // get data about the given path
if (fs.lstatSync(dirPath).isDirectory()) { const stats = fs.lstatSync(fileOrFolderPath)
recursiveParser(dirPath)
} else { // if it's a directory, call this function to every files/directories in it
throw Error("Initial path given does not lead to a directory") // 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(contentDirectoryPath, { recursive: true })
// eslint-disable-next-line no-empty
} catch (err) {}
try {
fs.unlinkSync(mapFilePath)
// eslint-disable-next-line no-empty
} catch (err) {}
// check if it's a directory and start recursive parse function
if (!fs.lstatSync(markdownPath).isDirectory())
throw Error("Invalid markdown path")
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[] = [] let dateKeys: string[] = []
for (const dateKey in result.date) { for (const dateKey in map.date) {
dateKeys.push(dateKey) dateKeys.push(dateKey)
} }
dateKeys = dateKeys.sort() dateKeys = dateKeys.sort()
const resultDate = result.date const TmpDate = map.date
result.date = {} map.date = {}
dateKeys.forEach( dateKeys.forEach((sortedDateKey) => {
(sortedDateKey) => (result.date[sortedDateKey] = resultDate[sortedDateKey]) map.date[sortedDateKey] = TmpDate[sortedDateKey]
) })
/** Step 3 // fill meta data
* write to src/data/posts.json 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 title: Quote NO.1
date: 2020-08-16 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> <div style="text-align: right"> <i>- Mahatma Ghanghi (1885)</i> </div>

View file

@ -1,13 +1,11 @@
--- ---
title: Quote NO.2 title: Quote NO.2
date: 2021-02-20 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. 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> <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> <br>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,6 @@
--- ---
title: My Quote NO.15 title: My Quote NO.15
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> The depth of a proverb is proportional to the depth of the reader's thoughts. > 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 title: My Quote NO.16
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> Words of wisdom deepens the more you think about it. > Words of wisdom deepens the more you think about it.

View file

@ -1,8 +1,6 @@
--- ---
title: My Quote NO.17 title: My Quote NO.17
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> God didn't bless us with the best, so let's do it ourself. > 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 title: My Quote NO.18
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> I got a purpose now, so why sit down?<br> > I got a purpose now, so why sit down?<br>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,6 @@
--- ---
title: My Quote NO.28 title: My Quote NO.28
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> Mind is like a sword. It will be dull if you stop sharpening it. > 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 title: My Quote NO.29
date: 2021-03-22 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. > 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 title: My Quote NO.2
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> Don't be great for your fame, but be famous for your greatness. > Don't be great for your fame, but be famous for your greatness.

View file

@ -1,8 +1,6 @@
--- ---
title: My Quote NO.30 title: My Quote NO.30
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> If you think too much about the answer, you'll forget what the question was. > 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 title: My Quote NO.31
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> People earns highest respect from me are those who appreciate critiques. > People earns highest respect from me are those who appreciate critiques.

View file

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

View file

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

View file

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

View file

@ -1,8 +1,6 @@
--- ---
title: My Quote NO.7 title: My Quote NO.7
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> The 1000 miles you've walked so far are not important. What's important is > 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 title: My Quote NO.8
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> Out of all the thing you've done and haven't done, which one do you regret more? > 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 title: My Quote NO.9
date: 2021-03-22 date: 2021-03-22
tags:
- quotes
--- ---
> People who don't know what they're talking about are the poorest people in the world. > 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 React from "react"
import marked from "marked" import marked from "marked"
import { Helmet } from "react-helmet-async" 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 NotFound from "./NotFound"
import Spinner from "../components/Spinner" import Spinner from "../components/Spinner"
const StyledTitle = styled.h1`
margin-bottom: 1rem;
`
interface PageProps {} interface PageProps {}
interface PageState { interface PageState {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
fetchedPage: any fetchedPage: any
isUnsearchable: boolean
loading: boolean loading: boolean
} }
@ -19,28 +26,48 @@ export default class Page extends React.Component<PageProps, PageState> {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
isUnsearchable: false,
fetchedPage: undefined, fetchedPage: undefined,
loading: true, loading: true,
} }
} }
async componentDidMount() { async componentDidMount() {
const url = location.pathname.replace(/\/$/, "") const url = location.pathname.replace(/\/$/, "") // remove trailing slash
const fetchedPage = posts.posts[url] // remove a trailing slash let _isUnsearchable = false
// fetch page
let fetchedPage = posts.posts[url]
if (!fetchedPage) { if (!fetchedPage) {
this.setState({ fetchedPage = posts.unsearchable[url]
loading: false, _isUnsearchable = true
}) this.setState({ isUnsearchable: true })
return if (!fetchedPage) {
this.setState({
loading: false,
})
return
}
}
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
} }
const fetched_content = (await import(`../data/posts${url}.json`))
.content
fetchedPage.content = fetched_content ? fetched_content : "No content" fetchedPage.content = fetched_content ? fetched_content : "No content"
fetchedPage.toc = fetchedPage?.toc ? fetchedPage.toc : undefined fetchedPage.toc = fetchedPage?.toc ? fetchedPage.toc : undefined
fetchedPage.title = fetchedPage?.title ? fetchedPage.title : "No title" 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({ this.setState({
fetchedPage: fetchedPage, fetchedPage: fetchedPage,
@ -69,12 +96,39 @@ export default class Page extends React.Component<PageProps, PageState> {
content={`${process.env.PUBLIC_URL}/icon/icon.svg`} content={`${process.env.PUBLIC_URL}/icon/icon.svg`}
/> />
</Helmet> </Helmet>
<div className="card main-content"> <div className="card main-content">
<h1>{this.state.fetchedPage.title}</h1> <StyledTitle>
{this.state.fetchedPage.title}
</StyledTitle>
{/* Post tags */}
<small> <small>
Published on {this.state.fetchedPage.date} by <table>
developomp {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> </small>
{/* Horizontal Separator */}
<hr /> <hr />
{ {
this.state.fetchedPage.toc && ( this.state.fetchedPage.toc && (

View file

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

View file

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