refactor(blog): move content gen code to its own package

This commit is contained in:
Kim, Jimin 2023-06-18 11:58:13 +09:00
parent c9c8cd35c1
commit 5ab6b93fa3
66 changed files with 460 additions and 380 deletions

View file

@ -1,35 +0,0 @@
import fs from "fs"
import {
contentDirectoryPath,
iconsDirectoryPath,
mapFilePath,
portfolioFilePath,
searchIndexFilePath,
} from "./config"
export default function clean() {
deleteDirectory(contentDirectoryPath)
deleteDirectory(iconsDirectoryPath)
deleteFile(mapFilePath)
deleteFile(portfolioFilePath)
deleteFile(searchIndexFilePath)
deleteFile("./public/img/skills.svg")
deleteFile("./public/img/projects.svg")
}
function deleteDirectory(path: string) {
try {
fs.rmSync(path, { recursive: true })
// eslint-disable-next-line no-empty
} catch (err) {}
}
function deleteFile(path: string) {
try {
fs.unlinkSync(path)
// eslint-disable-next-line no-empty
} catch (err) {}
}

View file

@ -3,12 +3,13 @@
"version": "0.0.0",
"private": true,
"scripts": {
"generate": "ts-node -O '{\"module\":\"commonjs\"}' --files ./generate",
"dev": "pnpm run generate && react-scripts start",
"build": "pnpm run generate && react-scripts build",
"cp": "cp -a ../../packages/blog-content/dist/public/. ./public",
"dev": "pnpm cp && react-scripts start",
"build": "pnpm cp && react-scripts build",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf build"
},
"dependencies": {
"@developomp-site/blog-content": "workspace:*",
"@developomp-site/theme": "workspace:*",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
@ -37,12 +38,10 @@
"@developomp-site/eslint-config": "workspace:*",
"@developomp-site/tsconfig": "workspace:*",
"@styled/typescript-styled-plugin": "^1.0.0",
"@types/ejs": "^3.1.1",
"@types/elasticlunr": "^0.9.5",
"@types/highlight.js": "^10.1.0",
"@types/jsdom": "^20.0.1",
"@types/katex": "^0.14.0",
"@types/markdown-it": "^12.2.3",
"@types/node": "^18.11.11",
"@types/react": "^18.0.26",
"@types/react-collapse": "^5.0.1",
@ -50,29 +49,10 @@
"@types/react-dom": "^18.0.9",
"@types/react-select": "^5.0.1",
"@types/styled-components": "^5.1.26",
"@types/svgo": "^3.0.0",
"@types/tinycolor2": "^1.4.3",
"ejs": "^3.1.8",
"gray-matter": "^4.0.3",
"jsdom": "^20.0.3",
"jspdf": "^2.5.1",
"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",
"prettier": "^2.8.1",
"read-time-estimate": "^0.0.3",
"simple-icons": "^7.21.0",
"svgo": "^3.0.2",
"tinycolor2": "^1.4.2",
"ts-node": "^10.9.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.9.4"
},

View file

@ -1,6 +1,7 @@
import dark from "@developomp-site/theme/dist/dark.json"
import light from "@developomp-site/theme/dist/light.json"
import { Badge } from "@developomp-site/blog-content/src/types/types"
import { useEffect, useState } from "react"
import styled from "styled-components"
@ -34,23 +35,16 @@ const StyledSVG = styled.div<{ isDark: boolean }>`
}
`
export interface Badge {
svg: string
hex: string
isDark: boolean
title: string
}
interface BadgeProps {
slug: string
}
const Badge = (props: BadgeProps) => {
export default (props: BadgeProps) => {
const [badgeData, setBadgeData] = useState<Badge | undefined>(undefined)
const { slug } = props
const getBadgeData = async () => {
return await require(`../data/icons/${slug}.json`)
return await require(`@developomp-site/blog-content/dist/icons/${slug}.json`)
}
useEffect(() => {
@ -71,5 +65,3 @@ const Badge = (props: BadgeProps) => {
</StyledBadge>
)
}
export default Badge

View file

@ -1,7 +1,7 @@
import styled from "styled-components"
import { Link } from "react-router-dom"
import { PostData } from "../../types/types"
import { PostData } from "@developomp-site/blog-content/src/types/types"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import {

View file

@ -0,0 +1,6 @@
import contentMapJson from "@developomp-site/blog-content/dist/map.json"
import { ContentMap } from "@developomp-site/blog-content/src/types/types"
const contentMap: ContentMap = contentMapJson
export default contentMap

View file

@ -2,7 +2,6 @@
* PostList.tsx
* show posts in recent order
*/
import type { Map } from "../../../types/types"
import { useCallback, useEffect, useState } from "react"
import { Helmet } from "react-helmet-async"
@ -11,9 +10,7 @@ import styled from "styled-components"
import PostCard from "../../components/PostCard"
import ShowMoreButton from "./ShowMoreButton"
import _map from "../../data/map.json"
const map: Map = _map
import contentMap from "../../contentMap"
const PostList = styled.div`
flex-direction: column;
@ -32,23 +29,23 @@ export default () => {
let postCount = 0
const postCards = [] as JSX.Element[]
for (const date of Object.keys(map.date).reverse()) {
for (const date of Object.keys(contentMap.date).reverse()) {
if (postCount >= howMany) break
const length = map.date[date].length
const length = contentMap.date[date].length
for (let i = 0; i < length; i++) {
if (postCount >= howMany) break
postCount++
const content_id = map.date[date][length - i - 1]
const content_id = contentMap.date[date][length - i - 1]
postCards.push(
<PostCard
key={content_id}
postData={{
content_id: content_id,
...map.posts[content_id],
...contentMap.posts[content_id],
}}
/>
)
@ -60,7 +57,7 @@ export default () => {
useEffect(() => {
loadPostCards()
setPostsLength(Object.keys(map.posts).length)
setPostsLength(Object.keys(contentMap.posts).length)
}, [howMany])
return (

View file

@ -7,7 +7,7 @@ import {
faHourglass,
} from "@fortawesome/free-solid-svg-icons"
import { PageData } from "../../../types/types"
import { PageData } from "@developomp-site/blog-content/src/types/types"
const StyledMetaContainer = styled.div`
color: ${({ theme }) => theme.theme.color.text.gray};

View file

@ -22,11 +22,9 @@ import {
import Meta from "./Meta"
import Toc from "./Toc"
import type { PageData, Map } from "../../../types/types"
import type { PageData } from "@developomp-site/blog-content/src/types/types"
import _map from "../../data/map.json"
const map: Map = _map
import contentMap from "../../contentMap"
const StyledTitle = styled.h1<{ pageType: PageType }>`
margin-bottom: 1rem;
@ -159,7 +157,7 @@ export default function Page() {
key={post}
postData={{
content_id: post,
...map.posts[post],
...contentMap.posts[post],
}}
/>
)

View file

@ -1,9 +1,8 @@
import portfolio from "../../data/portfolio.json"
import _map from "../../data/map.json"
import portfolio from "@developomp-site/blog-content/dist/portfolio.json"
import type { Map, PageData } from "../../../types/types"
import type { PageData } from "@developomp-site/blog-content/src/types/types"
const map: Map = _map
import contentMap from "../../contentMap"
export enum PageType {
POST,
@ -16,9 +15,13 @@ export enum PageType {
export async function fetchContent(pageType: PageType, url: string) {
try {
if (pageType == PageType.UNSEARCHABLE) {
return await import(`../../data/content/unsearchable${url}.json`)
return await import(
`@developomp-site/blog-content/dist/content/unsearchable${url}.json`
)
} else {
return await import(`../../data/content${url}.json`)
return await import(
`@developomp-site/blog-content/dist/content${url}.json`
)
}
} catch (err) {
return
@ -78,7 +81,7 @@ export function parsePageData(
// load and parse content differently depending on the content type
switch (pageType) {
case PageType.POST: {
const post = map.posts[content_id]
const post = contentMap.posts[content_id]
pageData.content = fetched_content.content
pageData.toc = fetched_content.toc
@ -95,11 +98,11 @@ export function parsePageData(
case PageType.SERIES: {
const seriesURL = content_id.slice(0, content_id.lastIndexOf("/"))
const curr = map.series[seriesURL].order.indexOf(content_id)
const curr = contentMap.series[seriesURL].order.indexOf(content_id)
const prev = curr - 1
const next = curr + 1
const post = map.posts[content_id]
const post = contentMap.posts[content_id]
pageData.content = fetched_content.content
pageData.toc = fetched_content.toc
@ -111,17 +114,18 @@ export function parsePageData(
pageData.tags = post.tags || []
pageData.seriesHome = seriesURL
pageData.prev = prev >= 0 ? map.series[seriesURL].order[prev] : undefined
pageData.prev =
prev >= 0 ? contentMap.series[seriesURL].order[prev] : undefined
pageData.next =
next < map.series[seriesURL].order.length
? map.series[seriesURL].order[next]
next < contentMap.series[seriesURL].order.length
? contentMap.series[seriesURL].order[next]
: undefined
break
}
case PageType.SERIES_HOME: {
const seriesData = map.series[content_id]
const seriesData = contentMap.series[content_id]
pageData.title = seriesData.title
pageData.content = fetched_content.content
@ -152,7 +156,7 @@ export function parsePageData(
}
case PageType.UNSEARCHABLE: {
pageData.title = map.unsearchable[content_id].title
pageData.title = contentMap.unsearchable[content_id].title
pageData.content = fetched_content.content
break

View file

@ -5,9 +5,9 @@ import MainContent from "../../components/MainContent"
import Badge from "../../components/Badge"
import ProjectCard from "./ProjectCard"
import portfolio from "../../data/portfolio.json"
import portfolio from "@developomp-site/blog-content/dist/portfolio.json"
import type { PortfolioProject } from "../../../types/types"
import type { PortfolioProject } from "@developomp-site/blog-content/src/types/types"
const Portfolio = () => {
const [projects, setProjects] = useState<JSX.Element[]>([])

View file

@ -5,7 +5,7 @@ import { Link } from "react-router-dom"
import Badge from "../../components/Badge"
import { cardCSS } from "../../components/Card"
import { PortfolioProject } from "../../../types/types"
import { PortfolioProject } from "@developomp-site/blog-content/src/types/types"
const StyledProjectCard = styled.div`
${cardCSS}

View file

@ -6,8 +6,7 @@ import { Range } from "react-date-range"
import elasticlunr from "elasticlunr" // search engine
import _map from "../../data/map.json"
import searchData from "../../data/search.json"
import searchData from "@developomp-site/blog-content/dist/search.json"
import Loading from "../../components/Loading"
import PostCard from "../../components/PostCard"
@ -17,13 +16,11 @@ import SearchBar from "./SearchBar"
import TagSelect, { TagsData } from "./TagSelect"
import { ClearDateButton, DateRangeControl, StyledDateRange } from "./DateRange"
import contentMap from "../../contentMap"
import "react-date-range/dist/styles.css"
import "react-date-range/dist/theme/default.css"
import type { Map } from "../../../types/types"
const map: Map = _map
const searchIndex = elasticlunr.Index.load(searchData as never)
export interface SearchParams {
@ -112,7 +109,7 @@ const Search = () => {
try {
const _postCards: JSX.Element[] = []
for (const res of searchIndex.search(searchInput)) {
const postData = map.posts[res.ref]
const postData = contentMap.posts[res.ref]
if (
postData && // if post data exists

View file

@ -2,13 +2,9 @@ import { useContext } from "react"
import styled from "styled-components"
import Select from "react-select"
import _map from "../../data/map.json"
import contentMap from "../../contentMap"
import { globalContext } from "../../globalContext"
import type { Map } from "../../../types/types"
const map: Map = _map
const StyledReactTagsContainer = styled.div`
width: 100%;
margin-top: 1.5rem;
@ -19,7 +15,7 @@ export interface TagsData {
label: string
}
const options: TagsData[] = map.meta.tags.map((elem) => ({
const options: TagsData[] = contentMap.meta.tags.map((elem) => ({
value: elem,
label: elem,
}))

View file

@ -24,5 +24,5 @@
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src/**/*", "types/**/*", "generate/**/*"]
"include": ["src/**/*", "types/**/*"]
}

View file

@ -0,0 +1,42 @@
{
"name": "@developomp-site/blog-content",
"version": "0.0.0",
"license": "MIT",
"files": [
"dist/**"
],
"scripts": {
"build": "ts-node -O '{\"module\":\"commonjs\"}' --files ./src",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"dependencies": {
"@developomp-site/tsconfig": "workspace:*",
"@types/ejs": "^3.1.1",
"@types/katex": "^0.14.0",
"@types/markdown-it": "^12.2.3",
"@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",
"svgo": "^3.0.2",
"tinycolor2": "^1.4.2",
"typescript": "^4.9.4"
},
"devDependencies": {
"@types/read-time-estimate": "^0.0.0"
}
}

View file

@ -1,5 +1,5 @@
export const markdownPath = "./markdown" // where it will look for markdown documents
export const outPath = "./src/data" // path to the json database
export const outPath = "./dist" // path to the json database
export const contentDirectoryPath = `${outPath}/content`
export const iconsDirectoryPath = `${outPath}/icons`

View file

@ -12,11 +12,10 @@ import { mapFilePath, markdownPath, portfolioFilePath } from "./config"
import { recursiveParse } from "./recursiveParse"
import { saveIndex } from "./searchIndex"
import postProcess from "./postProcess"
import clean from "./clean"
import { Map, ParseMode, SeriesMap, PortfolioData } from "../types/types"
import { ContentMap, ParseMode, PortfolioData, SeriesMap } from "./types/types"
export const map: Map = {
export const contentMap: ContentMap = {
date: {},
tags: {},
meta: {
@ -36,7 +35,10 @@ export const portfolioData: PortfolioData = {
* Delete previously generated files
*/
clean()
try {
fs.rmSync("dist", { recursive: true })
// eslint-disable-next-line no-empty
} catch (err) {}
/**
* Checking
@ -73,7 +75,7 @@ postProcess()
* Save results
*/
fs.writeFileSync(mapFilePath, JSON.stringify(map))
fs.writeFileSync(mapFilePath, JSON.stringify(contentMap))
fs.writeFileSync(
portfolioFilePath,
JSON.stringify({
@ -81,4 +83,5 @@ fs.writeFileSync(
skills: Array.from(portfolioData.skills),
})
)
saveIndex()

View file

@ -18,7 +18,7 @@ import "katex/contrib/mhchem" // chemical formula
import { JSDOM } from "jsdom" // HTML DOM parsing
import { nthIndex } from "./util"
import { MarkdownData, ParseMode } from "../types/types"
import { MarkdownData, ParseMode } from "./types/types"
const md = markdownIt({
// https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md

View file

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 639 B

Before After
Before After

View file

@ -4,10 +4,12 @@ import { readFileSync, writeFileSync } from "fs"
import icons from "simple-icons/icons"
import tinycolor from "tinycolor2"
import { map, seriesMap } from "."
import { Badge } from "../src/components/Badge"
import { contentMap, seriesMap } from "."
import { Badge } from "./types/types"
import skills from "./portfolio/skills.json"
import { writeToFile } from "./util"
export default function postProcess() {
sortDates()
@ -17,17 +19,17 @@ export default function postProcess() {
}
function sortDates() {
const TmpDate = map.date
map.date = {}
const TmpDate = contentMap.date
contentMap.date = {}
Object.keys(TmpDate)
.sort()
.forEach((sortedDateKey) => {
map.date[sortedDateKey] = TmpDate[sortedDateKey]
contentMap.date[sortedDateKey] = TmpDate[sortedDateKey]
})
}
function fillTags() {
map.meta.tags = Object.keys(map.tags)
contentMap.meta.tags = Object.keys(contentMap.tags)
}
function parseSeries() {
@ -43,8 +45,10 @@ function parseSeries() {
// series length and order
for (const seriesURL in seriesMap) {
map.series[seriesURL].length = seriesMap[seriesURL].length
map.series[seriesURL].order = seriesMap[seriesURL].map((item) => item.url)
contentMap.series[seriesURL].length = seriesMap[seriesURL].length
contentMap.series[seriesURL].order = seriesMap[seriesURL].map(
(item) => item.url
)
}
}
@ -55,7 +59,7 @@ function generatePortfolioSVGs() {
// todo: wait add ejs once it's available
const style = readFileSync("./generate/portfolio/style.css", "utf-8")
const style = readFileSync("./src/portfolio/style.css", "utf-8")
const data: {
[key: string]: Badge[] | { [key: string]: Badge[] }
@ -104,13 +108,13 @@ function generatePortfolioSVGs() {
}
const renderedSVG = ejs.render(
readFileSync("./generate/portfolio/skills.ejs", "utf-8"),
readFileSync("./src/portfolio/skills.ejs", "utf-8"),
{ style, data },
{ views: ["./generate/portfolio"] }
{ views: ["./src/portfolio"] }
)
writeFileSync(
"./public/img/skills.svg",
writeToFile(
"./dist/public/img/skills.svg",
optimize(renderedSVG, { multipass: true }).data
)
}

View file

@ -4,12 +4,13 @@ import readTimeEstimate from "read-time-estimate" // post read time estimation
import { path2FileOrFolderName, path2URL } from "../util"
import parseMarkdown from "../parseMarkdown"
import { ParseMode } from "../../types/types"
import parsePost from "./parsePost"
import parseSeries from "./parseSeries"
import parseUnsearchable from "./parseUnsearchable"
import parsePortfolio from "./parsePortfolio"
import { ParseMode } from "../types/types"
/**
* Data that's passed from {@link parseFile} to other function
*/

View file

@ -1,11 +1,12 @@
import { contentDirectoryPath } from "../config"
import { generateToc } from "../parseMarkdown"
import { PostData } from "../../types/types"
import { addDocument } from "../searchIndex"
import { writeToFile } from "../util"
import { map } from ".."
import { contentMap } from ".."
import { DataToPass } from "."
import { PostData } from "../types/types"
export default function parsePost(data: DataToPass): void {
const { urlPath, markdownRaw, markdownData, humanizedDuration, totalWords } =
data
@ -30,10 +31,10 @@ export default function parsePost(data: DataToPass): void {
})
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
if (map.date[YYYY_MM_DD]) {
map.date[YYYY_MM_DD].push(urlPath)
if (contentMap.date[YYYY_MM_DD]) {
contentMap.date[YYYY_MM_DD].push(urlPath)
} else {
map.date[YYYY_MM_DD] = [urlPath]
contentMap.date[YYYY_MM_DD] = [urlPath]
}
/**
@ -43,10 +44,10 @@ export default function parsePost(data: DataToPass): void {
postData.tags = markdownData.tags as string[]
if (postData.tags) {
postData.tags.forEach((tag) => {
if (map.tags[tag]) {
map.tags[tag].push(urlPath)
if (contentMap.tags[tag]) {
contentMap.tags[tag].push(urlPath)
} else {
map.tags[tag] = [urlPath]
contentMap.tags[tag] = [urlPath]
}
})
}
@ -55,7 +56,7 @@ export default function parsePost(data: DataToPass): void {
*
*/
map.posts[urlPath] = postData
contentMap.posts[urlPath] = postData
addDocument({
title: markdownData.title,
body: markdownData.content,

View file

@ -1,10 +1,11 @@
import { contentDirectoryPath } from "../config"
import { generateToc } from "../parseMarkdown"
import { PostData } from "../../types/types"
import { addDocument } from "../searchIndex"
import { writeToFile } from "../util"
import { map, seriesMap } from ".."
import { contentMap, seriesMap } from ".."
import { DataToPass } from "."
import { PostData } from "../types/types"
export default function parseSeries(data: DataToPass): void {
const {
@ -63,10 +64,10 @@ export default function parseSeries(data: DataToPass): void {
})
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
if (map.date[YYYY_MM_DD]) {
map.date[YYYY_MM_DD].push(urlPath)
if (contentMap.date[YYYY_MM_DD]) {
contentMap.date[YYYY_MM_DD].push(urlPath)
} else {
map.date[YYYY_MM_DD] = [urlPath]
contentMap.date[YYYY_MM_DD] = [urlPath]
}
/**
@ -76,10 +77,10 @@ export default function parseSeries(data: DataToPass): void {
postData.tags = markdownData.tags as string[]
if (postData.tags) {
postData.tags.forEach((tag) => {
if (map.tags[tag]) {
map.tags[tag].push(urlPath)
if (contentMap.tags[tag]) {
contentMap.tags[tag].push(urlPath)
} else {
map.tags[tag] = [urlPath]
contentMap.tags[tag] = [urlPath]
}
})
}
@ -94,18 +95,18 @@ export default function parseSeries(data: DataToPass): void {
url: urlPath,
})
map.posts[urlPath] = postData
contentMap.posts[urlPath] = postData
// series markdown starting with 0 is a series descriptor
if (isFileDescriptor) {
map.series[urlPath] = {
contentMap.series[urlPath] = {
...postData,
order: [],
length: 0,
}
} else {
// put series post in appropriate series
for (const key of Object.keys(map.series)) {
for (const key of Object.keys(contentMap.series)) {
if (urlPath.includes(key)) {
const index = parseInt(
_urlPath.slice(

View file

@ -1,7 +1,7 @@
import { contentDirectoryPath } from "../config"
import { addDocument } from "../searchIndex"
import { writeToFile } from "../util"
import { map } from ".."
import { contentMap } from ".."
import { DataToPass } from "."
export default function parseUnsearchable(data: DataToPass): void {
@ -17,7 +17,7 @@ export default function parseUnsearchable(data: DataToPass): void {
})
// Parse data that will be written to map.js
map.unsearchable[urlPath] = {
contentMap.unsearchable[urlPath] = {
title: markdownData.title as string,
}

View file

@ -1,4 +1,4 @@
export interface Map {
export interface ContentMap {
// key: YYYY-MM-DD
// value: url
date: { [key: string]: string[] }
@ -81,6 +81,13 @@ export interface PageData {
repo: string
}
export interface Badge {
svg: string
hex: string
isDark: boolean
title: string
}
/**
* Series
*/

View file

@ -0,0 +1,8 @@
{
"extends": "@developomp-site/tsconfig/node16.json",
"include": ["src"],
"compilerOptions": {
"resolveJsonModule": true
},
"exclude": ["dist", "node_modules"]
}

516
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff