added tags, changed data structure, separated unsearchable posts from regular posts
This commit is contained in:
parent
9922ff1fa4
commit
2d5582fda2
46 changed files with 463 additions and 342 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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/
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
title: games
|
|
||||||
date: 2020-08-16
|
|
||||||
---
|
|
||||||
|
|
||||||
- [TicTacToe](/games/tictactoe)
|
|
||||||
- [pong](/games/pong)
|
|
||||||
- [click](/games/click)
|
|
|
@ -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
|
|
9
source/markdown/posts/test post.md
Normal file
9
source/markdown/posts/test post.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
title: Test post
|
||||||
|
date: 2021-07-26
|
||||||
|
tags:
|
||||||
|
- tag1
|
||||||
|
- tag2
|
||||||
|
---
|
||||||
|
|
||||||
|
A post have title, date, tag, and content.
|
|
@ -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>
|
|
3
source/markdown/series/quotes/0.md
Normal file
3
source/markdown/series/quotes/0.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
title: quotes
|
||||||
|
---
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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 />
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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?
|
|
@ -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
|
|
@ -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?
|
|
@ -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.
|
31
source/src/components/Tag.tsx
Normal file
31
source/src/components/Tag.tsx
Normal 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} /> {this.props.text}
|
||||||
|
</StyledTag>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 && (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue