chore: change eslint & prettier config

This commit is contained in:
Kim, Jimin 2023-06-29 15:53:51 +09:00
parent fc827d74fe
commit b43871c516
103 changed files with 3581 additions and 3543 deletions

View file

@ -1,41 +1,41 @@
{
"name": "@developomp-site/blog-content",
"version": "0.0.0",
"license": "MIT",
"files": [
"dist/**"
],
"scripts": {
"build": "ts-node --experimental-specifier-resolution=node ./src",
"clean": "rm -rf .turbo node_modules dist"
},
"dependencies": {
"@developomp-site/tsconfig": "workspace:*",
"@types/ejs": "^3.1.1",
"@types/katex": "^0.14.0",
"@types/markdown-it": "^12.2.3",
"@types/read-time-estimate": "^0.0.0",
"@types/svgo": "^3.0.0",
"@types/tinycolor2": "^1.4.3",
"canvas": "^2.11.2",
"ejs": "^3.1.8",
"gray-matter": "^4.0.3",
"markdown-it": "^13.0.1",
"markdown-it-anchor": "^8.6.5",
"markdown-it-attrs": "^4.1.4",
"markdown-it-footnote": "^3.0.3",
"markdown-it-highlight-lines": "^1.0.2",
"markdown-it-mark": "^3.0.1",
"markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0",
"markdown-it-task-checkbox": "^1.0.6",
"markdown-it-texmath": "^1.0.0",
"markdown-toc": "^1.2.0",
"read-time-estimate": "^0.0.3",
"simple-icons": "^7.21.0",
"slugify": "^1.6.6",
"svgo": "^3.0.2",
"tinycolor2": "^1.4.2",
"typescript": "^4.9.4"
}
"name": "@developomp-site/blog-content",
"version": "0.0.0",
"license": "MIT",
"files": [
"dist/**"
],
"scripts": {
"build": "ts-node --experimental-specifier-resolution=node ./src",
"clean": "rm -rf .turbo node_modules dist"
},
"dependencies": {
"@developomp-site/tsconfig": "workspace:*",
"@types/ejs": "^3.1.1",
"@types/katex": "^0.14.0",
"@types/markdown-it": "^12.2.3",
"@types/read-time-estimate": "^0.0.0",
"@types/svgo": "^3.0.0",
"@types/tinycolor2": "^1.4.3",
"canvas": "^2.11.2",
"ejs": "^3.1.8",
"gray-matter": "^4.0.3",
"markdown-it": "^13.0.1",
"markdown-it-anchor": "^8.6.5",
"markdown-it-attrs": "^4.1.4",
"markdown-it-footnote": "^3.0.3",
"markdown-it-highlight-lines": "^1.0.2",
"markdown-it-mark": "^3.0.1",
"markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0",
"markdown-it-task-checkbox": "^1.0.6",
"markdown-it-texmath": "^1.0.0",
"markdown-toc": "^1.2.0",
"read-time-estimate": "^0.0.3",
"simple-icons": "^7.21.0",
"slugify": "^1.6.6",
"svgo": "^3.0.2",
"tinycolor2": "^1.4.2",
"typescript": "^4.9.4"
}
}

View file

@ -16,19 +16,19 @@ import postProcess from "./postProcess"
import { ContentMap, ParseMode, PortfolioData, SeriesMap } from "./types/types"
export const contentMap: ContentMap = {
date: {},
tags: {},
meta: {
tags: [],
},
posts: {},
series: {},
unsearchable: {},
date: {},
tags: {},
meta: {
tags: [],
},
posts: {},
series: {},
unsearchable: {},
}
export const seriesMap: SeriesMap = {}
export const portfolioData: PortfolioData = {
skills: new Set(),
projects: {},
skills: new Set(),
projects: {},
}
/**
@ -36,8 +36,8 @@ export const portfolioData: PortfolioData = {
*/
try {
fs.rmSync("dist", { recursive: true })
// eslint-disable-next-line no-empty
fs.rmSync("dist", { recursive: true })
// eslint-disable-next-line no-empty
} catch (err) {}
/**
@ -45,16 +45,16 @@ try {
*/
if (!fs.lstatSync(markdownPath).isDirectory())
throw Error("Invalid markdown path")
throw Error("Invalid markdown path")
if (!fs.lstatSync(markdownPath + "/posts").isDirectory())
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
if (!fs.lstatSync(markdownPath + "/unsearchable").isDirectory())
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
if (!fs.lstatSync(markdownPath + "/series").isDirectory())
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
/**
* Parse
@ -77,11 +77,11 @@ postProcess()
fs.writeFileSync(mapFilePath, JSON.stringify(contentMap))
fs.writeFileSync(
portfolioFilePath,
JSON.stringify({
...portfolioData,
skills: Array.from(portfolioData.skills),
})
portfolioFilePath,
JSON.stringify({
...portfolioData,
skills: Array.from(portfolioData.skills),
})
)
saveIndex()

View file

@ -24,37 +24,37 @@ import { MarkdownData, ParseMode } from "./types/types"
const slugifyIt = (s: string) => slugify(s, { lower: true, strict: true })
const md = markdownIt({
// https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
highlight: (str, lang) => {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value
// eslint-disable-next-line no-empty
} catch (error) {}
}
// https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
highlight: (str, lang) => {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value
// eslint-disable-next-line no-empty
} catch (error) {}
}
return "" // use external default escaping
},
html: true,
return "" // use external default escaping
},
html: true,
})
.use(markdownItTexMath, {
engine: katex,
delimiters: "dollars",
})
.use(markdownItAnchor, {
permalink: markdownItAnchor.permalink.ariaHidden({
placement: "before",
symbol: "#",
renderHref: (s) => `#${slugifyIt(s)}`,
}),
slugify: slugifyIt,
})
.use(markdownItTaskCheckbox)
.use(markDownItMark)
.use(markdownItSub)
.use(markdownItSup)
.use(highlightLines)
.use(markdownItFootnote)
.use(markdownItTexMath, {
engine: katex,
delimiters: "dollars",
})
.use(markdownItAnchor, {
permalink: markdownItAnchor.permalink.ariaHidden({
placement: "before",
symbol: "#",
renderHref: (s) => `#${slugifyIt(s)}`,
}),
slugify: slugifyIt,
})
.use(markdownItTaskCheckbox)
.use(markDownItMark)
.use(markdownItSub)
.use(markdownItSup)
.use(highlightLines)
.use(markdownItFootnote)
/**
* parse the front matter if it exists
@ -64,70 +64,70 @@ const md = markdownIt({
* @param {ParseMode} mode
*/
export default function parseMarkdown(
markdownRaw: string,
path: string,
mode: ParseMode
markdownRaw: string,
path: string,
mode: ParseMode
): MarkdownData {
const fileHasFrontMatter = markdownRaw.startsWith("---")
const fileHasFrontMatter = markdownRaw.startsWith("---")
const frontMatter = fileHasFrontMatter
? matter(markdownRaw.slice(0, nthIndex(markdownRaw, "---", 2) + 3)).data
: {}
const frontMatter = fileHasFrontMatter
? matter(markdownRaw.slice(0, nthIndex(markdownRaw, "---", 2) + 3)).data
: {}
if (fileHasFrontMatter) {
if (mode != ParseMode.PORTFOLIO) {
if (!frontMatter.title)
throw Error(`Title is not defined in file: ${path}`)
if (fileHasFrontMatter) {
if (mode != ParseMode.PORTFOLIO) {
if (!frontMatter.title)
throw Error(`Title is not defined in file: ${path}`)
if (mode != ParseMode.UNSEARCHABLE && !frontMatter.date)
throw Error(`Date is not defined in file: ${path}`)
}
if (mode != ParseMode.UNSEARCHABLE && !frontMatter.date)
throw Error(`Date is not defined in file: ${path}`)
}
if (mode === ParseMode.PORTFOLIO) {
if (frontMatter.overview) {
frontMatter.overview = md.render(frontMatter.overview)
}
}
}
if (mode === ParseMode.PORTFOLIO) {
if (frontMatter.overview) {
frontMatter.overview = md.render(frontMatter.overview)
}
}
}
//
// work with rendered DOM
//
//
// work with rendered DOM
//
const dom = new JSDOM(
md.render(
fileHasFrontMatter
? markdownRaw.slice(nthIndex(markdownRaw, "---", 2) + 3)
: markdownRaw
) || ""
)
const dom = new JSDOM(
md.render(
fileHasFrontMatter
? markdownRaw.slice(nthIndex(markdownRaw, "---", 2) + 3)
: markdownRaw
) || ""
)
// add .hljs class to all block codes
// add .hljs class to all block codes
dom.window.document.querySelectorAll("pre > code").forEach((item) => {
item.classList.add("hljs")
})
dom.window.document.querySelectorAll("pre > code").forEach((item) => {
item.classList.add("hljs")
})
// add parent div to tables (horizontally scroll table on small displays)
// add parent div to tables (horizontally scroll table on small displays)
dom.window.document.querySelectorAll("table").forEach((item) => {
// `element` is the element you want to wrap
const parent = item.parentNode
if (!parent) return // stop if table doesn't have a parent node
const wrapper = dom.window.document.createElement("div")
wrapper.style.overflowX = "auto"
dom.window.document.querySelectorAll("table").forEach((item) => {
// `element` is the element you want to wrap
const parent = item.parentNode
if (!parent) return // stop if table doesn't have a parent node
const wrapper = dom.window.document.createElement("div")
wrapper.style.overflowX = "auto"
parent.replaceChild(wrapper, item)
wrapper.appendChild(item)
})
parent.replaceChild(wrapper, item)
wrapper.appendChild(item)
})
frontMatter.content = dom.window.document.documentElement.innerHTML
frontMatter.content = dom.window.document.documentElement.innerHTML
return frontMatter as MarkdownData
return frontMatter as MarkdownData
}
export function generateToc(markdownRaw: string): string {
return md.render(toc(markdownRaw).content, {
slugify: slugifyIt,
})
return md.render(toc(markdownRaw).content, {
slugify: slugifyIt,
})
}

View file

@ -1,21 +1,21 @@
{
"Programming Languages": [
"javascript",
"typescript",
"python",
"rust",
"csharp C#"
],
"Web Front End": ["react", "svelte", "tailwindcss Tailwind"],
"Desktop Front End": ["gtk", "electron", "tauri"],
"Back End": ["firebase"],
"DevOps": ["docker", "githubactions GH Actions"],
"Game Development": ["unity"],
"Etc": [
"figma",
"markdown",
"notion",
"google Google-Fu",
"discord Discord Bot"
]
"Programming Languages": [
"javascript",
"typescript",
"python",
"rust",
"csharp C#"
],
"Web Front End": ["react", "svelte", "tailwindcss Tailwind"],
"Desktop Front End": ["gtk", "electron", "tauri"],
"Back End": ["firebase"],
"DevOps": ["docker", "githubactions GH Actions"],
"Game Development": ["unity"],
"Etc": [
"figma",
"markdown",
"notion",
"google Google-Fu",
"discord Discord Bot"
]
}

View file

@ -1,9 +1,9 @@
svg {
/* from github */
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji;
font-size: 14px;
color: #777777;
/* from github */
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji;
font-size: 14px;
color: #777777;
}
h1,
@ -12,50 +12,50 @@ h3,
h4,
h5,
h6 {
text-align: center;
text-align: center;
}
.items-wrapper {
display: grid;
grid-template-columns: repeat(5, 1fr);
display: grid;
grid-template-columns: repeat(5, 1fr);
column-gap: 10px;
row-gap: 15px;
column-gap: 10px;
row-gap: 15px;
}
.badge {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
justify-content: center;
align-items: center;
text-align: center;
gap: 5px;
gap: 5px;
}
.badge-box {
display: flex;
display: flex;
justify-content: center;
align-items: center;
justify-content: center;
align-items: center;
border-radius: 7px;
border-radius: 7px;
width: 70px;
height: 70px;
width: 70px;
height: 70px;
}
.icon-container > svg {
height: 40px !important;
height: 40px !important;
}
.white {
color: white;
fill: white;
color: white;
fill: white;
}
.black {
color: black;
fill: black;
color: black;
fill: black;
}

View file

@ -12,127 +12,127 @@ import skills from "./portfolio/skills.json"
import { writeToFile } from "./util"
export default function postProcess() {
sortDates()
fillTags()
parseSeries()
generatePortfolioSVGs()
sortDates()
fillTags()
parseSeries()
generatePortfolioSVGs()
}
function sortDates() {
const TmpDate = contentMap.date
contentMap.date = {}
Object.keys(TmpDate)
.sort()
.forEach((sortedDateKey) => {
contentMap.date[sortedDateKey] = TmpDate[sortedDateKey]
})
const TmpDate = contentMap.date
contentMap.date = {}
Object.keys(TmpDate)
.sort()
.forEach((sortedDateKey) => {
contentMap.date[sortedDateKey] = TmpDate[sortedDateKey]
})
}
function fillTags() {
contentMap.meta.tags = Object.keys(contentMap.tags)
contentMap.meta.tags = Object.keys(contentMap.tags)
}
function parseSeries() {
// sort series map
for (const seriesURL in seriesMap) {
seriesMap[seriesURL].sort((a, b) => {
if (a.index < b.index) return -1
if (a.index > b.index) return 1
// sort series map
for (const seriesURL in seriesMap) {
seriesMap[seriesURL].sort((a, b) => {
if (a.index < b.index) return -1
if (a.index > b.index) return 1
return 0
})
}
return 0
})
}
// series length and order
for (const seriesURL in seriesMap) {
contentMap.series[seriesURL].length = seriesMap[seriesURL].length
contentMap.series[seriesURL].order = seriesMap[seriesURL].map(
(item) => item.url
)
}
// series length and order
for (const seriesURL in seriesMap) {
contentMap.series[seriesURL].length = seriesMap[seriesURL].length
contentMap.series[seriesURL].order = seriesMap[seriesURL].map(
(item) => item.url
)
}
}
function generatePortfolioSVGs() {
/**
* render skills.svg
*/
/**
* render skills.svg
*/
// todo: wait add ejs once it's available
// todo: wait add ejs once it's available
const style = readFileSync("./src/portfolio/style.css", "utf-8")
const style = readFileSync("./src/portfolio/style.css", "utf-8")
const data: {
[key: string]: Badge[] | { [key: string]: Badge[] }
} = {}
const data: {
[key: string]: Badge[] | { [key: string]: Badge[] }
} = {}
// C O G N I T O - H A Z A R D
// THIS PART OF THE CODE WAS WRITTEN IN 3 AM
// C O G N I T O - H A Z A R D
// C O G N I T O - H A Z A R D
// THIS PART OF THE CODE WAS WRITTEN IN 3 AM
// C O G N I T O - H A Z A R D
for (const key in skills) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (skills[key] instanceof Array) {
if (!data[key]) {
data[key] = []
}
for (const key in skills) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (skills[key] instanceof Array) {
if (!data[key]) {
data[key] = []
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
;(skills[key] as string[]).forEach((badge) =>
(data[key] as Badge[]).push(parseBadge(badge))
)
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
for (const subKey in skills[key]) {
if (!data[key]) data[key] = {}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
;(skills[key] as string[]).forEach((badge) =>
(data[key] as Badge[]).push(parseBadge(badge))
)
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
for (const subKey in skills[key]) {
if (!data[key]) data[key] = {}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!data[key][subKey]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data[key][subKey] = []
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!data[key][subKey]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data[key][subKey] = []
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
skills[key][subKey].forEach((badge: string) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(data[key][subKey] as Badge[]).push(parseBadge(badge))
)
}
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
skills[key][subKey].forEach((badge: string) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(data[key][subKey] as Badge[]).push(parseBadge(badge))
)
}
}
}
const renderedSVG = ejs.render(
readFileSync("./src/portfolio/skills.ejs", "utf-8"),
{ style, data },
{ views: ["./src/portfolio"] }
)
const renderedSVG = ejs.render(
readFileSync("./src/portfolio/skills.ejs", "utf-8"),
{ style, data },
{ views: ["./src/portfolio"] }
)
writeToFile(
"./dist/public/img/skills.svg",
optimize(renderedSVG, { multipass: true }).data
)
writeToFile(
"./dist/public/img/skills.svg",
optimize(renderedSVG, { multipass: true }).data
)
}
function parseBadge(badgeRaw: string): Badge {
const isMultiWord = badgeRaw.includes(" ")
const words = badgeRaw.split(" ")
const slug = words[0]
const isMultiWord = badgeRaw.includes(" ")
const words = badgeRaw.split(" ")
const slug = words[0]
// @ts-ignore
const icon = icons["si" + slug[0].toUpperCase() + slug.slice(1)]
// @ts-ignore
const icon = icons["si" + slug[0].toUpperCase() + slug.slice(1)]
const color = tinycolor(icon.hex).lighten(5).desaturate(5)
const color = tinycolor(icon.hex).lighten(5).desaturate(5)
return {
svg: icon.svg,
hex: color.toHexString(),
isDark: color.isDark(),
title: isMultiWord ? words.slice(1).join(" ") : icon.title,
}
return {
svg: icon.svg,
hex: color.toHexString(),
isDark: color.isDark(),
title: isMultiWord ? words.slice(1).join(" ") : icon.title,
}
}

View file

@ -15,15 +15,15 @@ import { ParseMode } from "../types/types"
* Data that's passed from {@link parseFile} to other function
*/
export interface DataToPass {
path: string
urlPath: string
markdownRaw: string
markdownData: {
content: string
[key: string]: unknown
}
humanizedDuration: string
totalWords: number
path: string
urlPath: string
markdownRaw: string
markdownData: {
content: string
[key: string]: unknown
}
humanizedDuration: string
totalWords: number
}
/**
@ -33,23 +33,23 @@ export interface DataToPass {
* @param {string} path - path of file or folder
*/
export function recursiveParse(mode: ParseMode, path: string): void {
// get name of the file or folder that's currently being parsed
const fileOrFolderName = path2FileOrFolderName(path)
// get name of the file or folder that's currently being parsed
const fileOrFolderName = path2FileOrFolderName(path)
// stop if the file or folder starts with a underscore
if (fileOrFolderName.startsWith("_")) return
// stop if the file or folder starts with a underscore
if (fileOrFolderName.startsWith("_")) return
const stats = fs.lstatSync(path)
const stats = fs.lstatSync(path)
// 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(path).map((childPath) => {
recursiveParse(mode, `${path}/${childPath}`)
})
} else if (stats.isFile()) {
parseFile(mode, path)
}
// 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(path).map((childPath) => {
recursiveParse(mode, `${path}/${childPath}`)
})
} else if (stats.isFile()) {
parseFile(mode, path)
}
}
/**
@ -59,50 +59,50 @@ export function recursiveParse(mode: ParseMode, path: string): void {
* @param {string} path - path of the markdown file
*/
function parseFile(mode: ParseMode, path: string): void {
// stop if it is not a markdown file
if (!path.endsWith(".md")) {
console.log(`Ignoring non markdown file at: ${path}`)
return
}
// stop if it is not a markdown file
if (!path.endsWith(".md")) {
console.log(`Ignoring non markdown file at: ${path}`)
return
}
/**
* Parse markdown
*/
/**
* Parse markdown
*/
const markdownRaw = fs.readFileSync(path, "utf8")
const markdownData = parseMarkdown(markdownRaw, path, mode)
const { humanizedDuration, totalWords } = readTimeEstimate(
markdownData.content,
275,
12,
500,
["img", "Image"]
)
const markdownRaw = fs.readFileSync(path, "utf8")
const markdownData = parseMarkdown(markdownRaw, path, mode)
const { humanizedDuration, totalWords } = readTimeEstimate(
markdownData.content,
275,
12,
500,
["img", "Image"]
)
const dataToPass: DataToPass = {
path,
urlPath: path2URL(path),
markdownRaw,
markdownData,
humanizedDuration,
totalWords,
}
const dataToPass: DataToPass = {
path,
urlPath: path2URL(path),
markdownRaw,
markdownData,
humanizedDuration,
totalWords,
}
switch (mode) {
case ParseMode.POSTS:
parsePost(dataToPass)
break
switch (mode) {
case ParseMode.POSTS:
parsePost(dataToPass)
break
case ParseMode.SERIES:
parseSeries(dataToPass)
break
case ParseMode.SERIES:
parseSeries(dataToPass)
break
case ParseMode.UNSEARCHABLE:
parseUnsearchable(dataToPass)
break
case ParseMode.UNSEARCHABLE:
parseUnsearchable(dataToPass)
break
case ParseMode.PORTFOLIO:
parseProjects(dataToPass)
break
}
case ParseMode.PORTFOLIO:
parseProjects(dataToPass)
break
}
}

View file

@ -8,65 +8,70 @@ import { DataToPass } from "."
import { PostData } from "../types/types"
export default function parsePost(data: DataToPass): void {
const { urlPath, markdownRaw, markdownData, humanizedDuration, totalWords } =
data
const {
urlPath,
markdownRaw,
markdownData,
humanizedDuration,
totalWords,
} = data
const postData: PostData = {
title: markdownData.title as string,
date: "",
readTime: humanizedDuration,
wordCount: totalWords,
tags: [],
}
const postData: PostData = {
title: markdownData.title as string,
date: "",
readTime: humanizedDuration,
wordCount: totalWords,
tags: [],
}
/**
* Dates
*/
/**
* Dates
*/
const postDate = new Date(markdownData.date as string)
postData.date = postDate.toLocaleString("default", {
month: "short",
day: "numeric",
year: "numeric",
})
const postDate = new Date(markdownData.date as string)
postData.date = postDate.toLocaleString("default", {
month: "short",
day: "numeric",
year: "numeric",
})
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
if (contentMap.date[YYYY_MM_DD]) {
contentMap.date[YYYY_MM_DD].push(urlPath)
} else {
contentMap.date[YYYY_MM_DD] = [urlPath]
}
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
if (contentMap.date[YYYY_MM_DD]) {
contentMap.date[YYYY_MM_DD].push(urlPath)
} else {
contentMap.date[YYYY_MM_DD] = [urlPath]
}
/**
* Tags
*/
/**
* Tags
*/
postData.tags = markdownData.tags as string[]
if (postData.tags) {
postData.tags.forEach((tag) => {
if (contentMap.tags[tag]) {
contentMap.tags[tag].push(urlPath)
} else {
contentMap.tags[tag] = [urlPath]
}
})
}
postData.tags = markdownData.tags as string[]
if (postData.tags) {
postData.tags.forEach((tag) => {
if (contentMap.tags[tag]) {
contentMap.tags[tag].push(urlPath)
} else {
contentMap.tags[tag] = [urlPath]
}
})
}
/**
*
*/
/**
*
*/
contentMap.posts[urlPath] = postData
addDocument({
title: markdownData.title,
body: markdownData.content,
url: urlPath,
})
writeToFile(
`${contentDirectoryPath}${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
toc: generateToc(markdownRaw),
})
)
contentMap.posts[urlPath] = postData
addDocument({
title: markdownData.title,
body: markdownData.content,
url: urlPath,
})
writeToFile(
`${contentDirectoryPath}${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
toc: generateToc(markdownRaw),
})
)
}

View file

@ -9,46 +9,46 @@ import { portfolioData } from ".."
import { DataToPass } from "."
export default function parseProjects(data: DataToPass): void {
const { urlPath, markdownRaw, markdownData } = data
const { urlPath, markdownRaw, markdownData } = data
if (markdownData.badges) {
;(markdownData.badges as string[]).forEach((slug) => {
// todo: handle cases when icon is not on simple-icons
const icon: SimpleIcon =
// @ts-ignore
icons["si" + slug[0].toUpperCase() + slug.slice(1)]
if (markdownData.badges) {
;(markdownData.badges as string[]).forEach((slug) => {
// todo: handle cases when icon is not on simple-icons
const icon: SimpleIcon =
// @ts-ignore
icons["si" + slug[0].toUpperCase() + slug.slice(1)]
portfolioData.skills.add(slug)
portfolioData.skills.add(slug)
const color = tinycolor(icon.hex).lighten(5).desaturate(5)
const color = tinycolor(icon.hex).lighten(5).desaturate(5)
// save svg icon
writeToFile(
`${iconsDirectoryPath}/${icon.slug}.json`,
JSON.stringify({
svg: icon.svg,
hex: color.toHexString(),
isDark: color.isDark(),
title: icon.title,
})
)
})
}
// save svg icon
writeToFile(
`${iconsDirectoryPath}/${icon.slug}.json`,
JSON.stringify({
svg: icon.svg,
hex: color.toHexString(),
isDark: color.isDark(),
title: icon.title,
})
)
})
}
// remove /projects/ prefix
portfolioData.projects[urlPath.replace("/projects/", "")] = {
name: markdownData.name as string,
image: markdownData.image as string,
overview: markdownData.overview as string,
badges: (markdownData.badges as string[]) || [],
repo: (markdownData.repo as string) || "",
}
// remove /projects/ prefix
portfolioData.projects[urlPath.replace("/projects/", "")] = {
name: markdownData.name as string,
image: markdownData.image as string,
overview: markdownData.overview as string,
badges: (markdownData.badges as string[]) || [],
repo: (markdownData.repo as string) || "",
}
writeToFile(
`${contentDirectoryPath}${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
toc: generateToc(markdownRaw),
})
)
writeToFile(
`${contentDirectoryPath}${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
toc: generateToc(markdownRaw),
})
)
}

View file

@ -8,140 +8,141 @@ import { DataToPass } from "."
import { PostData } from "../types/types"
export default function parseSeries(data: DataToPass): void {
const {
path,
urlPath: _urlPath,
markdownRaw,
markdownData,
humanizedDuration,
totalWords,
} = data
const {
path,
urlPath: _urlPath,
markdownRaw,
markdownData,
humanizedDuration,
totalWords,
} = data
// last part of the url without the slash
let lastPath = _urlPath.slice(_urlPath.lastIndexOf("/") + 1)
if (!lastPath.includes("_") && !lastPath.startsWith("0"))
throw Error(`Invalid series file name at: "${path}"`)
// last part of the url without the slash
let lastPath = _urlPath.slice(_urlPath.lastIndexOf("/") + 1)
if (!lastPath.includes("_") && !lastPath.startsWith("0"))
throw Error(`Invalid series file name at: "${path}"`)
// if file is a series descriptor or not (not = regular series post)
const isFileDescriptor = lastPath.startsWith("0") && !lastPath.includes("_")
// if file is a series descriptor or not (not = regular series post)
const isFileDescriptor = lastPath.startsWith("0") && !lastPath.includes("_")
// series post url
if (isFileDescriptor) {
lastPath = ""
} else {
lastPath = lastPath
.slice(lastPath.indexOf("_") + 1) // get string after the series index
.replace(/\/$/, "") // remove trailing slash
}
// series post url
if (isFileDescriptor) {
lastPath = ""
} else {
lastPath = lastPath
.slice(lastPath.indexOf("_") + 1) // get string after the series index
.replace(/\/$/, "") // remove trailing slash
}
// get url until right before the lastPath
const urlUntilLastPath = _urlPath.slice(0, _urlPath.lastIndexOf("/") + 1)
// get url until right before the lastPath
const urlUntilLastPath = _urlPath.slice(0, _urlPath.lastIndexOf("/") + 1)
// remove trailing slash if it's a regular series post
const urlPath =
(isFileDescriptor
? urlUntilLastPath.replace(/\/$/, "")
: urlUntilLastPath) + lastPath
// remove trailing slash if it's a regular series post
const urlPath =
(isFileDescriptor
? urlUntilLastPath.replace(/\/$/, "")
: urlUntilLastPath) + lastPath
// todo: separate interface for series descriptor (no word count and read time)
const postData: PostData = {
title: markdownData.title as string,
date: "",
readTime: humanizedDuration,
wordCount: totalWords,
tags: [],
}
// todo: separate interface for series descriptor (no word count and read time)
const postData: PostData = {
title: markdownData.title as string,
date: "",
readTime: humanizedDuration,
wordCount: totalWords,
tags: [],
}
/**
* Date
*/
/**
* Date
*/
const postDate = new Date(markdownData.date as string)
postData.date = postDate.toLocaleString("default", {
month: "short",
day: "numeric",
year: "numeric",
})
const postDate = new Date(markdownData.date as string)
postData.date = postDate.toLocaleString("default", {
month: "short",
day: "numeric",
year: "numeric",
})
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
if (contentMap.date[YYYY_MM_DD]) {
contentMap.date[YYYY_MM_DD].push(urlPath)
} else {
contentMap.date[YYYY_MM_DD] = [urlPath]
}
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
if (contentMap.date[YYYY_MM_DD]) {
contentMap.date[YYYY_MM_DD].push(urlPath)
} else {
contentMap.date[YYYY_MM_DD] = [urlPath]
}
/**
* Tags
*/
/**
* Tags
*/
postData.tags = markdownData.tags as string[]
if (postData.tags) {
postData.tags.forEach((tag) => {
if (contentMap.tags[tag]) {
contentMap.tags[tag].push(urlPath)
} else {
contentMap.tags[tag] = [urlPath]
}
})
}
postData.tags = markdownData.tags as string[]
if (postData.tags) {
postData.tags.forEach((tag) => {
if (contentMap.tags[tag]) {
contentMap.tags[tag].push(urlPath)
} else {
contentMap.tags[tag] = [urlPath]
}
})
}
/**
*
*/
/**
*
*/
addDocument({
title: markdownData.title,
body: markdownData.content,
url: urlPath,
})
addDocument({
title: markdownData.title,
body: markdownData.content,
url: urlPath,
})
contentMap.posts[urlPath] = postData
contentMap.posts[urlPath] = postData
// series markdown starting with 0 is a series descriptor
if (isFileDescriptor) {
contentMap.series[urlPath] = {
...postData,
order: [],
length: 0,
}
} else {
// put series post in appropriate series
for (const key of Object.keys(contentMap.series)) {
if (urlPath.includes(key)) {
const index = parseInt(
_urlPath.slice(
_urlPath.lastIndexOf("/") + 1,
_urlPath.lastIndexOf("_")
)
)
// series markdown starting with 0 is a series descriptor
if (isFileDescriptor) {
contentMap.series[urlPath] = {
...postData,
order: [],
length: 0,
}
} else {
// put series post in appropriate series
for (const key of Object.keys(contentMap.series)) {
if (urlPath.includes(key)) {
const index = parseInt(
_urlPath.slice(
_urlPath.lastIndexOf("/") + 1,
_urlPath.lastIndexOf("_")
)
)
if (isNaN(index)) throw Error(`Invalid series index at: ${path}`)
if (isNaN(index))
throw Error(`Invalid series index at: ${path}`)
const itemToPush = {
index: index,
url: urlPath,
}
const itemToPush = {
index: index,
url: urlPath,
}
if (seriesMap[key]) {
seriesMap[key].push(itemToPush)
} else {
seriesMap[key] = [itemToPush]
}
if (seriesMap[key]) {
seriesMap[key].push(itemToPush)
} else {
seriesMap[key] = [itemToPush]
}
break
}
}
}
break
}
}
}
/**
* Save content
*/
/**
* Save content
*/
writeToFile(
`${contentDirectoryPath}${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
toc: generateToc(markdownRaw),
})
)
writeToFile(
`${contentDirectoryPath}${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
toc: generateToc(markdownRaw),
})
)
}

View file

@ -5,30 +5,30 @@ import { contentMap } from ".."
import { DataToPass } from "."
export default function parseUnsearchable(data: DataToPass): void {
const { urlPath: _urlPath, markdownData } = data
const { urlPath: _urlPath, markdownData } = data
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
addDocument({
title: markdownData.title,
body: markdownData.content,
url: urlPath,
})
addDocument({
title: markdownData.title,
body: markdownData.content,
url: urlPath,
})
// Parse data that will be written to map.js
contentMap.unsearchable[urlPath] = {
title: markdownData.title as string,
}
// Parse data that will be written to map.js
contentMap.unsearchable[urlPath] = {
title: markdownData.title as string,
}
/**
* Save content
*/
/**
* Save content
*/
writeToFile(
`${contentDirectoryPath}/unsearchable${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
})
)
writeToFile(
`${contentDirectoryPath}/unsearchable${urlPath}.json`,
JSON.stringify({
content: markdownData.content,
})
)
}

View file

@ -8,19 +8,19 @@ import elasticlunr from "elasticlunr"
import { searchIndexFilePath } from "./config"
const elasticlunrIndex = elasticlunr(function () {
this.addField("title" as never)
this.addField("body" as never)
this.setRef("url" as never)
this.addField("title" as never)
this.addField("body" as never)
this.setRef("url" as never)
})
export function addDocument(doc: {
title?: unknown
body?: string
url?: string
title?: unknown
body?: string
url?: string
}) {
elasticlunrIndex.addDoc(doc)
elasticlunrIndex.addDoc(doc)
}
export function saveIndex() {
fs.writeFileSync(searchIndexFilePath, JSON.stringify(elasticlunrIndex))
fs.writeFileSync(searchIndexFilePath, JSON.stringify(elasticlunrIndex))
}

View file

@ -1,4 +1,4 @@
declare module "markdown-it-texmath" {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function texmath(md: MarkdownIt, ...params: any[]): void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function texmath(md: MarkdownIt, ...params: any[]): void
}

View file

@ -1,6 +1,6 @@
declare module "markdown-toc" {
export default function toc(str: string): {
json: JSON
content: string
}
export default function toc(str: string): {
json: JSON
content: string
}
}

View file

@ -1,32 +1,32 @@
export interface ContentMap {
// key: YYYY-MM-DD
// value: url
date: { [key: string]: string[] }
// key: YYYY-MM-DD
// value: url
date: { [key: string]: string[] }
// key: tag name
// value: url
tags: {
[key: string]: string[]
}
// key: tag name
// value: url
tags: {
[key: string]: string[]
}
// list of all meta data
meta: {
tags: 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]: PostData
}
// searchable, non-series posts
// must have a post date
// tag is not required
posts: {
[key: string]: PostData
}
// series posts have "previous post" and "next post" button so they need to be ordered
series: { [key: string]: Series }
// series posts have "previous post" and "next post" button so they need to be ordered
series: { [key: string]: Series }
// urls of unsearchable posts
// it is here to quickly check if a post exists or not
unsearchable: { [key: string]: { title: string } }
// urls of unsearchable posts
// it is here to quickly check if a post exists or not
unsearchable: { [key: string]: { title: string } }
}
/**
@ -34,58 +34,58 @@ export interface ContentMap {
*/
export enum ParseMode {
POSTS,
SERIES,
UNSEARCHABLE,
PORTFOLIO,
POSTS,
SERIES,
UNSEARCHABLE,
PORTFOLIO,
}
export interface MarkdownData {
content: string
[key: string]: unknown
content: string
[key: string]: unknown
}
export interface PostData {
title: string
date: string
readTime: string
wordCount: number
tags?: string[]
title: string
date: string
readTime: string
wordCount: number
tags?: string[]
}
export interface PageData {
title: string
date: string
readTime: string
wordCount: number
tags: string[]
toc?: string
content: string
title: string
date: string
readTime: string
wordCount: number
tags: string[]
toc?: string
content: string
// series
// series
seriesHome: string
prev?: string
next?: string
seriesHome: string
prev?: string
next?: string
// series home
// series home
order: string[]
length: number
order: string[]
length: number
// portfolio
// portfolio
image: string // image url
overview: string
badges: string[]
repo: string
image: string // image url
overview: string
badges: string[]
repo: string
}
export interface Badge {
svg: string
hex: string
isDark: boolean
title: string
svg: string
hex: string
isDark: boolean
title: string
}
/**
@ -93,23 +93,23 @@ export interface Badge {
*/
export interface Series {
title: string
date: string
readTime: string
wordCount: number
order: string[]
length: number
tags?: string[]
title: string
date: string
readTime: string
wordCount: number
order: string[]
length: number
tags?: string[]
}
export interface SeriesMap {
// key: url
[key: string]: SeriesEntry[]
// key: url
[key: string]: SeriesEntry[]
}
export interface SeriesEntry {
index: number
url: string
index: number
url: string
}
/**
@ -117,25 +117,25 @@ export interface SeriesEntry {
*/
export interface PortfolioData {
// a set of valid simple icons slug
skills: Set<string>
// a set of valid simple icons slug
skills: Set<string>
// key: url
projects: {
[key: string]: PortfolioProject
}
// key: url
projects: {
[key: string]: PortfolioProject
}
}
export interface PortfolioOverview {
// link to my github
github: string
description: string
// link to my github
github: string
description: string
}
export interface PortfolioProject {
name: string
image: string // url to the image
overview: string
badges: string[] // array of valid simpleIcons slug
repo: string // url of the git repository
name: string
image: string // url to the image
overview: string
badges: string[] // array of valid simpleIcons slug
repo: string // url of the git repository
}

View file

@ -9,9 +9,9 @@ import { markdownPath } from "./config"
* @param {string} pathToConvert
*/
export function path2URL(pathToConvert: string): string {
return `/${relative(markdownPath, pathToConvert)}`
.replace(/\.[^/.]+$/, "") // remove the file extension
.replace(/ /g, "-") // replace all space with a dash
return `/${relative(markdownPath, pathToConvert)}`
.replace(/\.[^/.]+$/, "") // remove the file extension
.replace(/ /g, "-") // replace all space with a dash
}
/**
@ -20,33 +20,34 @@ export function path2URL(pathToConvert: string): string {
* @param {string} inputPath - path to parse
*/
export function path2FileOrFolderName(inputPath: string): string {
// remove trailing slash
if (inputPath[-1] == "/") inputPath = inputPath.slice(0, inputPath.length - 1)
// remove trailing slash
if (inputPath[-1] == "/")
inputPath = inputPath.slice(0, inputPath.length - 1)
// get the last section
return inputPath.slice(inputPath.lastIndexOf("/") + 1)
// get the last section
return inputPath.slice(inputPath.lastIndexOf("/") + 1)
}
// gets the nth occurance of a pattern in string
// returns -1 if nothing is found
// https://stackoverflow.com/a/14482123/12979111
export function nthIndex(str: string, pat: string, n: number) {
let i = -1
let i = -1
while (n-- && i++ < str.length) {
i = str.indexOf(pat, i)
if (i < 0) break
}
while (n-- && i++ < str.length) {
i = str.indexOf(pat, i)
if (i < 0) break
}
return i
return i
}
export function writeToFile(filePath: string, dataToWrite: string) {
// create directory to put the files
fs.mkdirSync(filePath.slice(0, filePath.lastIndexOf("/")), {
recursive: true,
})
// create directory to put the files
fs.mkdirSync(filePath.slice(0, filePath.lastIndexOf("/")), {
recursive: true,
})
// write content to the file
fs.writeFileSync(filePath, dataToWrite)
// write content to the file
fs.writeFileSync(filePath, dataToWrite)
}

View file

@ -1,16 +1,16 @@
{
"extends": "@developomp-site/tsconfig/node16.json",
"include": ["src"],
"ts-node": {
"esm": true
},
"compilerOptions": {
"moduleResolution": "Node",
"isolatedModules": false,
"noImplicitAny": false,
"esModuleInterop": true,
"allowJs": true,
"resolveJsonModule": true
},
"exclude": ["dist", "node_modules"]
"extends": "@developomp-site/tsconfig/node16.json",
"include": ["src"],
"ts-node": {
"esm": true
},
"compilerOptions": {
"moduleResolution": "Node",
"isolatedModules": false,
"noImplicitAny": false,
"esModuleInterop": true,
"allowJs": true,
"resolveJsonModule": true
},
"exclude": ["dist", "node_modules"]
}