chore: change eslint & prettier config
This commit is contained in:
parent
fc827d74fe
commit
b43871c516
103 changed files with 3581 additions and 3543 deletions
14
.eslintrc.js
14
.eslintrc.js
|
@ -1,9 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: ["@developomp-site/eslint-config"],
|
extends: ["@developomp-site/eslint-config"],
|
||||||
settings: {
|
settings: {
|
||||||
next: {
|
next: {
|
||||||
rootDir: ["apps/*/"],
|
rootDir: ["apps/*/"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
"blog": [
|
"blog": [
|
||||||
"developomp-site-blog"
|
"developomp-site-blog"
|
||||||
],
|
],
|
||||||
"portfolio": [
|
"portfolio": [
|
||||||
"developomp-site-portfolio"
|
"developomp-site-portfolio"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"etags": {}
|
"etags": {}
|
||||||
}
|
}
|
||||||
|
|
56
.github/workflows/deploy.yml
vendored
56
.github/workflows/deploy.yml
vendored
|
@ -2,37 +2,37 @@
|
||||||
|
|
||||||
name: Deploy pages
|
name: Deploy pages
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
if: ${{ github.repository_owner == 'developomp' }}
|
if: ${{ github.repository_owner == 'developomp' }}
|
||||||
name: Deploy
|
name: Deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Deploy to Firebase
|
- name: Deploy to Firebase
|
||||||
uses: w9jds/firebase-action@master
|
uses: w9jds/firebase-action@master
|
||||||
with:
|
with:
|
||||||
args: deploy
|
args: deploy
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -8,10 +8,10 @@ node_modules
|
||||||
*.log
|
*.log
|
||||||
.next
|
.next
|
||||||
dist
|
dist
|
||||||
|
build
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
.env
|
.env
|
||||||
.cache
|
.cache
|
||||||
server/dist
|
|
||||||
public/dist
|
|
||||||
storybook-static/
|
storybook-static/
|
||||||
|
.svelte-kit/
|
||||||
|
|
19
.prettierrc
19
.prettierrc
|
@ -1,4 +1,19 @@
|
||||||
{
|
{
|
||||||
"useTabs": true,
|
"useTabs": false,
|
||||||
"semi": false
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.md",
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ".firebaserc",
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
22
.vscode/extensions.json
vendored
22
.vscode/extensions.json
vendored
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"naumovs.color-highlight",
|
"naumovs.color-highlight",
|
||||||
"streetsidesoftware.code-spell-checker",
|
"streetsidesoftware.code-spell-checker",
|
||||||
"aaron-bond.better-comments",
|
"aaron-bond.better-comments",
|
||||||
"styled-components.vscode-styled-components",
|
"styled-components.vscode-styled-components",
|
||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"unifiedjs.vscode-mdx",
|
"unifiedjs.vscode-mdx",
|
||||||
"svelte.svelte-vscode"
|
"svelte.svelte-vscode"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
148
.vscode/settings.json
vendored
148
.vscode/settings.json
vendored
|
@ -1,76 +1,76 @@
|
||||||
{
|
{
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnPaste": true,
|
"editor.formatOnPaste": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.insertSpaces": false,
|
"editor.insertSpaces": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": true
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"bspwm",
|
"bspwm",
|
||||||
"cairographics",
|
"cairographics",
|
||||||
"classnet",
|
"classnet",
|
||||||
"deno",
|
"deno",
|
||||||
"developomp",
|
"developomp",
|
||||||
"developomp's",
|
"developomp's",
|
||||||
"dompurify",
|
"dompurify",
|
||||||
"elasticlunr",
|
"elasticlunr",
|
||||||
"Exyle",
|
"Exyle",
|
||||||
"exyleio",
|
"exyleio",
|
||||||
"Fontawesome",
|
"Fontawesome",
|
||||||
"Fonticons",
|
"Fonticons",
|
||||||
"fontsource",
|
"fontsource",
|
||||||
"fortawesome",
|
"fortawesome",
|
||||||
"Freedesktop",
|
"Freedesktop",
|
||||||
"GDSC",
|
"GDSC",
|
||||||
"githubactions",
|
"githubactions",
|
||||||
"githubpages",
|
"githubpages",
|
||||||
"gnubash",
|
"gnubash",
|
||||||
"godotengine",
|
"godotengine",
|
||||||
"heroicon",
|
"heroicon",
|
||||||
"hljs",
|
"hljs",
|
||||||
"hongik",
|
"hongik",
|
||||||
"hoofd",
|
"hoofd",
|
||||||
"inqling",
|
"inqling",
|
||||||
"Jimin",
|
"Jimin",
|
||||||
"katex",
|
"katex",
|
||||||
"Librewolf",
|
"Librewolf",
|
||||||
"linaria",
|
"linaria",
|
||||||
"nodedotjs",
|
"nodedotjs",
|
||||||
"noto",
|
"noto",
|
||||||
"pnpm",
|
"pnpm",
|
||||||
"pocketbase",
|
"pocketbase",
|
||||||
"polybar",
|
"polybar",
|
||||||
"Pomky",
|
"Pomky",
|
||||||
"precompress",
|
"precompress",
|
||||||
"rainmeter",
|
"rainmeter",
|
||||||
"sxhkd",
|
"sxhkd",
|
||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"tauri",
|
"tauri",
|
||||||
"texmath",
|
"texmath",
|
||||||
"tinycolor",
|
"tinycolor",
|
||||||
"tsup",
|
"tsup",
|
||||||
"Turborepo",
|
"Turborepo",
|
||||||
"ungoogled",
|
"ungoogled",
|
||||||
"unixporn",
|
"unixporn",
|
||||||
"wbtimeline",
|
"wbtimeline",
|
||||||
"webassembly",
|
"webassembly",
|
||||||
"wouter",
|
"wouter",
|
||||||
"YYYYMMDD"
|
"YYYYMMDD"
|
||||||
],
|
],
|
||||||
"eslint.workingDirectories": [{ "mode": "auto" }],
|
"eslint.workingDirectories": [{ "mode": "auto" }],
|
||||||
"[svg]": {
|
"[svg]": {
|
||||||
"editor.defaultFormatter": "jock.svg"
|
"editor.defaultFormatter": "jock.svg"
|
||||||
},
|
},
|
||||||
// prevent tailwind-related warnings
|
// prevent tailwind-related warnings
|
||||||
"css.lint.unknownAtRules": "ignore",
|
"css.lint.unknownAtRules": "ignore",
|
||||||
"less.lint.unknownAtRules": "ignore",
|
"less.lint.unknownAtRules": "ignore",
|
||||||
"scss.lint.unknownAtRules": "ignore",
|
"scss.lint.unknownAtRules": "ignore",
|
||||||
// for .ejs files
|
// for .ejs files
|
||||||
"html.validate.styles": false,
|
"html.validate.styles": false,
|
||||||
"color-highlight.markerType": "outline",
|
"color-highlight.markerType": "outline",
|
||||||
"[dotenv]": {
|
"[dotenv]": {
|
||||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:json/recommended",
|
"plugin:json/recommended",
|
||||||
"prettier"
|
"prettier"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"node": {
|
"node": {
|
||||||
"tryExtensions": [".js", ".jsx", ".json"]
|
"tryExtensions": [".js", ".jsx", ".json"]
|
||||||
},
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "18.0"
|
"version": "18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true
|
"jsx": true
|
||||||
},
|
},
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"plugins": ["@typescript-eslint"],
|
"plugins": ["@typescript-eslint"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
"@typescript-eslint/no-empty-interface": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
"react/react-in-jsx-scope": ["off"]
|
"react/react-in-jsx-scope": ["off"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,71 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/blog",
|
"name": "@developomp-site/blog",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cp": "cp -a ../../packages/blog-content/dist/public/. ./public",
|
"cp": "cp -a ../../packages/blog-content/dist/public/. ./public",
|
||||||
"dev": "pnpm cp && react-scripts start",
|
"dev": "pnpm cp && react-scripts start",
|
||||||
"build": "pnpm cp && react-scripts build",
|
"build": "pnpm cp && react-scripts build",
|
||||||
"clean": "rm -rf .turbo build node_modules"
|
"clean": "rm -rf .turbo build node_modules"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@developomp-site/blog-content": "workspace:*",
|
"@developomp-site/blog-content": "workspace:*",
|
||||||
"@developomp-site/theme": "workspace:*",
|
"@developomp-site/theme": "workspace:*",
|
||||||
"@fontsource/noto-sans-kr": "^5.0.3",
|
"@fontsource/noto-sans-kr": "^5.0.3",
|
||||||
"@fontsource/source-code-pro": "^5.0.3",
|
"@fontsource/source-code-pro": "^5.0.3",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"elasticlunr": "^0.9.5",
|
"elasticlunr": "^0.9.5",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
"katex": "^0.16.4",
|
"katex": "^0.16.4",
|
||||||
"local-storage-fallback": "^4.1.2",
|
"local-storage-fallback": "^4.1.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-collapse": "^5.1.1",
|
"react-collapse": "^5.1.1",
|
||||||
"react-date-range": "^1.4.0",
|
"react-date-range": "^1.4.0",
|
||||||
"react-device-detect": "^2.2.2",
|
"react-device-detect": "^2.2.2",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-helmet-async": "^1.3.0",
|
"react-helmet-async": "^1.3.0",
|
||||||
"react-router-dom": "^6.4.5",
|
"react-router-dom": "^6.4.5",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"react-select": "^5.7.0",
|
"react-select": "^5.7.0",
|
||||||
"react-tooltip": "^4.5.1",
|
"react-tooltip": "^4.5.1",
|
||||||
"styled-components": "^5.3.6"
|
"styled-components": "^5.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@developomp-site/tsconfig": "workspace:*",
|
"@developomp-site/tsconfig": "workspace:*",
|
||||||
"@styled/typescript-styled-plugin": "^1.0.0",
|
"@styled/typescript-styled-plugin": "^1.0.0",
|
||||||
"@types/elasticlunr": "^0.9.5",
|
"@types/elasticlunr": "^0.9.5",
|
||||||
"@types/highlight.js": "^10.1.0",
|
"@types/highlight.js": "^10.1.0",
|
||||||
"@types/jsdom": "^20.0.1",
|
"@types/jsdom": "^20.0.1",
|
||||||
"@types/katex": "^0.14.0",
|
"@types/katex": "^0.14.0",
|
||||||
"@types/node": "^18.11.11",
|
"@types/node": "^18.11.11",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-collapse": "^5.0.1",
|
"@types/react-collapse": "^5.0.1",
|
||||||
"@types/react-date-range": "^1.4.4",
|
"@types/react-date-range": "^1.4.4",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"@types/react-select": "^5.0.1",
|
"@types/react-select": "^5.0.1",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.26",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"simple-icons": "^7.21.0",
|
"simple-icons": "^7.21.0",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
"not dead",
|
"not dead",
|
||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
],
|
],
|
||||||
"development": [
|
"development": [
|
||||||
"last 1 chrome version",
|
"last 1 chrome version",
|
||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/icon/icon_circle.svg" />
|
<link rel="icon" href="%PUBLIC_URL%/icon/icon_circle.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta property="og:image" content="%PUBLIC_URL%/img/icon.png" />
|
<meta property="og:image" content="%PUBLIC_URL%/img/icon.png" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
<title>pomp</title>
|
<title>pomp</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
English: Oops! It seems like JavaScript is not enabled!
|
English: Oops! It seems like JavaScript is not enabled!
|
||||||
<br />
|
<br />
|
||||||
Korean: 이런! 자바스크립트를 사용할 수 없습니다!
|
Korean: 이런! 자바스크립트를 사용할 수 없습니다!
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -21,74 +21,77 @@ import GlobalStyle from "./styles/globalStyle"
|
||||||
import { globalContext } from "./globalContext"
|
import { globalContext } from "./globalContext"
|
||||||
|
|
||||||
const IENotSupported = styled.p`
|
const IENotSupported = styled.p`
|
||||||
margin: auto;
|
margin: auto;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: ${(props) => props.theme.theme.font.sansSerif};
|
font-family: ${(props) => props.theme.theme.font.sansSerif};
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledContentContainer = styled.div`
|
const StyledContentContainer = styled.div`
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-bottom: 3rem;
|
margin-bottom: 3rem;
|
||||||
margin-top: 5rem;
|
margin-top: 5rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { globalState } = useContext(globalContext)
|
const { globalState } = useContext(globalContext)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// set loading to false if all fonts are loaded
|
// set loading to false if all fonts are loaded
|
||||||
// checks if document.fonts.onloadingdone is supported on the browser
|
// checks if document.fonts.onloadingdone is supported on the browser
|
||||||
if (typeof document.fonts.onloadingdone != undefined) {
|
if (typeof document.fonts.onloadingdone != undefined) {
|
||||||
document.fonts.onloadingdone = () => {
|
document.fonts.onloadingdone = () => {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (isIE)
|
if (isIE)
|
||||||
return (
|
return (
|
||||||
<IENotSupported>
|
<IENotSupported>
|
||||||
Internet Explorer is <b>not supported.</b>
|
Internet Explorer is <b>not supported.</b>
|
||||||
</IENotSupported>
|
</IENotSupported>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
theme={{
|
theme={{
|
||||||
currentTheme: globalState.currentTheme,
|
currentTheme: globalState.currentTheme,
|
||||||
theme: globalState.currentTheme === "dark" ? darkTheme : lightTheme,
|
theme:
|
||||||
}}
|
globalState.currentTheme === "dark"
|
||||||
>
|
? darkTheme
|
||||||
<Helmet>
|
: lightTheme,
|
||||||
<meta property="og:site_name" content="developomp" />
|
}}
|
||||||
<meta property="og:title" content="Home" />
|
>
|
||||||
<meta property="og:description" content="developomp's blog" />
|
<Helmet>
|
||||||
<meta name="description" content="developomp's blog" />
|
<meta property="og:site_name" content="developomp" />
|
||||||
</Helmet>
|
<meta property="og:title" content="Home" />
|
||||||
|
<meta property="og:description" content="developomp's blog" />
|
||||||
|
<meta name="description" content="developomp's blog" />
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
<StyledContentContainer>
|
<StyledContentContainer>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Loading />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Home />} />
|
<Route index element={<Home />} />
|
||||||
<Route path="search" element={<Search />} />
|
<Route path="search" element={<Search />} />
|
||||||
<Route path="404" element={<NotFound />} />
|
<Route path="404" element={<NotFound />} />
|
||||||
<Route path="loading" element={<Loading />} />
|
<Route path="loading" element={<Loading />} />
|
||||||
<Route path="*" element={<Page />} />
|
<Route path="*" element={<Page />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)}
|
)}
|
||||||
</StyledContentContainer>
|
</StyledContentContainer>
|
||||||
<Footer />
|
<Footer />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import styled, { css } from "styled-components"
|
import styled, { css } from "styled-components"
|
||||||
|
|
||||||
export const cardCSS = css`
|
export const cardCSS = css`
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.currentTheme ? theme.theme.component.card.color.background : "white"};
|
theme.currentTheme
|
||||||
padding: 2rem;
|
? theme.theme.component.card.color.background
|
||||||
border-radius: 6px;
|
: "white"};
|
||||||
box-shadow: ${({ theme }) =>
|
padding: 2rem;
|
||||||
theme.currentTheme === "dark"
|
border-radius: 6px;
|
||||||
? "0 4px 10px rgb(0 0 0 / 30%), 0 0 1px rgb(0 0 0 / 30%)"
|
box-shadow: ${({ theme }) =>
|
||||||
: "0 4px 10px rgb(0 0 0 / 5%), 0 0 1px rgb(0 0 0 / 10%)"};
|
theme.currentTheme === "dark"
|
||||||
|
? "0 4px 10px rgb(0 0 0 / 30%), 0 0 1px rgb(0 0 0 / 30%)"
|
||||||
|
: "0 4px 10px rgb(0 0 0 / 5%), 0 0 1px rgb(0 0 0 / 10%)"};
|
||||||
|
|
||||||
@media screen and (max-width: ${({ theme }) =>
|
@media screen and (max-width: ${({ theme }) =>
|
||||||
theme.theme.maxDisplayWidth.mobile}) {
|
theme.theme.maxDisplayWidth.mobile}) {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default styled.div`
|
export default styled.div`
|
||||||
${cardCSS}
|
${cardCSS}
|
||||||
`
|
`
|
||||||
|
|
|
@ -3,41 +3,41 @@ import styled from "styled-components"
|
||||||
import GithubLinkIcon from "../GithubLinkIcon"
|
import GithubLinkIcon from "../GithubLinkIcon"
|
||||||
|
|
||||||
const StyledFooter = styled.footer`
|
const StyledFooter = styled.footer`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
// congratulation. You've found the lucky 7s
|
// congratulation. You've found the lucky 7s
|
||||||
min-height: 7.77rem;
|
min-height: 7.77rem;
|
||||||
max-height: 7.77rem;
|
max-height: 7.77rem;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.footer.color.background};
|
theme.theme.component.footer.color.background};
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledFooterContainer = styled.div`
|
const StyledFooterContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 1rem 0 1rem;
|
padding: 0 1rem 0 1rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: gray;
|
color: gray;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: ${({ theme }) => theme.theme.maxDisplayWidth.desktop};
|
max-width: ${({ theme }) => theme.theme.maxDisplayWidth.desktop};
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<StyledFooter>
|
<StyledFooter>
|
||||||
<StyledFooterContainer>
|
<StyledFooterContainer>
|
||||||
<div>
|
<div>
|
||||||
Created by <b>developomp</b>
|
Created by <b>developomp</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GithubLinkIcon link="https://github.com/developomp/developomp-site" />
|
<GithubLinkIcon link="https://github.com/developomp/developomp-site" />
|
||||||
</StyledFooterContainer>
|
</StyledFooterContainer>
|
||||||
</StyledFooter>
|
</StyledFooter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,31 +5,31 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
import { faGithub } from "@fortawesome/free-brands-svg-icons"
|
||||||
|
|
||||||
const StyledGithubLink = styled.a<{ size?: string }>`
|
const StyledGithubLink = styled.a<{ size?: string }>`
|
||||||
font-size: ${(props) => props.size || "2.5rem"};
|
font-size: ${(props) => props.size || "2.5rem"};
|
||||||
color: ${({ theme }) =>
|
color: ${({ theme }) =>
|
||||||
theme.currentTheme === "dark" ? "grey" : "lightgrey"};
|
theme.currentTheme === "dark" ? "grey" : "lightgrey"};
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
color: ${({ theme }) => theme.theme.color.text.highContrast};
|
color: ${({ theme }) => theme.theme.color.text.highContrast};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
link: string
|
link: string
|
||||||
size?: string
|
size?: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ link, size, children }: Props) => {
|
export default ({ link, size, children }: Props) => {
|
||||||
return (
|
return (
|
||||||
<StyledGithubLink
|
<StyledGithubLink
|
||||||
aria-label="GitHub repository"
|
aria-label="GitHub repository"
|
||||||
size={size}
|
size={size}
|
||||||
href={link}
|
href={link}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faGithub} />
|
<FontAwesomeIcon icon={faGithub} />
|
||||||
{children}
|
{children}
|
||||||
</StyledGithubLink>
|
</StyledGithubLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,16 @@ import ThemeToggleButton from "./ThemeToggleButton"
|
||||||
import SearchButton from "./SearchButton"
|
import SearchButton from "./SearchButton"
|
||||||
|
|
||||||
const RightButtons = styled.div`
|
const RightButtons = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<RightButtons>
|
<RightButtons>
|
||||||
<ThemeToggleButton />
|
<ThemeToggleButton />
|
||||||
<SearchButton />
|
<SearchButton />
|
||||||
</RightButtons>
|
</RightButtons>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,25 +6,25 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
const SearchButton = () => {
|
const SearchButton = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
data-tip
|
data-tip
|
||||||
data-for="search"
|
data-for="search"
|
||||||
to="/search"
|
to="/search"
|
||||||
aria-label="go to search page"
|
aria-label="go to search page"
|
||||||
>
|
>
|
||||||
<HeaderButton>
|
<HeaderButton>
|
||||||
<FontAwesomeIcon icon={faSearch} />
|
<FontAwesomeIcon icon={faSearch} />
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ReactTooltip id="search" type="dark" effect="solid">
|
<ReactTooltip id="search" type="dark" effect="solid">
|
||||||
<span>Search</span>
|
<span>Search</span>
|
||||||
</ReactTooltip>
|
</ReactTooltip>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchButton
|
export default SearchButton
|
||||||
|
|
|
@ -10,42 +10,42 @@ import { ActionsEnum, globalContext } from "../../../globalContext"
|
||||||
import { HeaderButtonCSS } from "../HeaderButton"
|
import { HeaderButtonCSS } from "../HeaderButton"
|
||||||
|
|
||||||
const StyledThemeButton = styled.button`
|
const StyledThemeButton = styled.button`
|
||||||
${HeaderButtonCSS}
|
${HeaderButtonCSS}
|
||||||
border: none;
|
border: none;
|
||||||
width: 72px;
|
width: 72px;
|
||||||
|
|
||||||
${({ theme }) =>
|
${({ theme }) =>
|
||||||
theme.currentTheme === "dark" ? "transform: scaleX(-1)" : ""};
|
theme.currentTheme === "dark" ? "transform: scaleX(-1)" : ""};
|
||||||
`
|
`
|
||||||
|
|
||||||
const ThemeToggleButton = () => {
|
const ThemeToggleButton = () => {
|
||||||
const { globalState, dispatch } = useContext(globalContext)
|
const { globalState, dispatch } = useContext(globalContext)
|
||||||
const theme = globalState.currentTheme
|
const theme = globalState.currentTheme
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledThemeButton
|
<StyledThemeButton
|
||||||
data-tip
|
data-tip
|
||||||
aria-label="theme toggle"
|
aria-label="theme toggle"
|
||||||
data-for="theme"
|
data-for="theme"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionsEnum.UPDATE_THEME,
|
type: ActionsEnum.UPDATE_THEME,
|
||||||
payload: theme === "dark" ? "light" : "dark",
|
payload: theme === "dark" ? "light" : "dark",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{theme == "dark" && <FontAwesomeIcon icon={faMoon} />}
|
{theme == "dark" && <FontAwesomeIcon icon={faMoon} />}
|
||||||
{theme == "light" && <FontAwesomeIcon icon={faSun} />}
|
{theme == "light" && <FontAwesomeIcon icon={faSun} />}
|
||||||
</StyledThemeButton>
|
</StyledThemeButton>
|
||||||
|
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<ReactTooltip id="theme" type="dark" effect="solid">
|
<ReactTooltip id="theme" type="dark" effect="solid">
|
||||||
<span>Using {theme} theme</span>
|
<span>Using {theme} theme</span>
|
||||||
</ReactTooltip>
|
</ReactTooltip>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ThemeToggleButton
|
export default ThemeToggleButton
|
||||||
|
|
|
@ -9,50 +9,52 @@ import Sidebar from "../Sidebar"
|
||||||
import Buttons from "./Buttons"
|
import Buttons from "./Buttons"
|
||||||
|
|
||||||
const Header = styled.header`
|
const Header = styled.header`
|
||||||
/* set z index to arbitrarily high value to prevent other components from drawing over it */
|
/* set z index to arbitrarily high value to prevent other components from drawing over it */
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.ui.color.background.default};
|
theme.theme.component.ui.color.background.default};
|
||||||
color: ${({ theme }) => theme.theme.color.text.default};
|
color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
|
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
|
||||||
`
|
`
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
|
|
||||||
/* account for 20px scrollbar width */
|
/* account for 20px scrollbar width */
|
||||||
@media only screen and (min-width: calc(${({ theme }) =>
|
@media only screen and (min-width: calc(${({ theme }) =>
|
||||||
theme.theme.maxDisplayWidth.desktop} + 20px)) {
|
theme.theme.maxDisplayWidth.desktop} + 20px)) {
|
||||||
width: calc(${({ theme }) => theme.theme.maxDisplayWidth.desktop} - 20px);
|
width: calc(
|
||||||
}
|
${({ theme }) => theme.theme.maxDisplayWidth.desktop} - 20px
|
||||||
|
);
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const Icon = styled.img`
|
const Icon = styled.img`
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<Header>
|
<Header>
|
||||||
<Container>
|
<Container>
|
||||||
<Link to="/" aria-label="homepage">
|
<Link to="/" aria-label="homepage">
|
||||||
<Icon src="/icon/icon_circle.svg" alt="logo" />
|
<Icon src="/icon/icon_circle.svg" alt="logo" />
|
||||||
</Link>
|
</Link>
|
||||||
<Nav />
|
<Nav />
|
||||||
<Buttons />
|
<Buttons />
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</Container>
|
</Container>
|
||||||
<ReadProgress />
|
<ReadProgress />
|
||||||
</Header>
|
</Header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,39 +5,39 @@
|
||||||
import styled, { css } from "styled-components"
|
import styled, { css } from "styled-components"
|
||||||
|
|
||||||
export const HeaderButtonCSS = css`
|
export const HeaderButtonCSS = css`
|
||||||
/* style */
|
/* style */
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
/* size */
|
/* size */
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 2.5rem;
|
min-width: 2.5rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 1rem 0 1rem;
|
padding: 0 1rem 0 1rem;
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
/* color */
|
/* color */
|
||||||
|
|
||||||
color: ${({ theme }) => theme.theme.color.text.default};
|
color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.ui.color.background.default};
|
theme.theme.component.ui.color.background.default};
|
||||||
|
|
||||||
/* animation */
|
/* animation */
|
||||||
|
|
||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.ui.color.background.hover};
|
theme.theme.component.ui.color.background.hover};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default styled.div`
|
export default styled.div`
|
||||||
${HeaderButtonCSS}
|
${HeaderButtonCSS}
|
||||||
`
|
`
|
||||||
|
|
|
@ -6,29 +6,29 @@ import HeaderButton from "./HeaderButton"
|
||||||
import NavbarData from "../../data/NavbarData"
|
import NavbarData from "../../data/NavbarData"
|
||||||
|
|
||||||
const Nav = styled.div`
|
const Nav = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
@media only screen and (max-width: ${({ theme }) =>
|
@media only screen and (max-width: ${({ theme }) =>
|
||||||
theme.theme.maxDisplayWidth.mobile}) {
|
theme.theme.maxDisplayWidth.mobile}) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<Nav>
|
<Nav>
|
||||||
{NavbarData.map(({ path, title }, index) => {
|
{NavbarData.map(({ path, title }, index) => {
|
||||||
return path.at(0) === "/" ? (
|
return path.at(0) === "/" ? (
|
||||||
<Link key={index} to={path}>
|
<Link key={index} to={path}>
|
||||||
<HeaderButton>{title}</HeaderButton>
|
<HeaderButton>{title}</HeaderButton>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<a key={index} target="_blank" href={path}>
|
<a key={index} target="_blank" href={path}>
|
||||||
<HeaderButton>{title}</HeaderButton>
|
<HeaderButton>{title}</HeaderButton>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Nav>
|
</Nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,15 @@ import { useLocation } from "react-router-dom"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
const Background = styled.div`
|
const Background = styled.div`
|
||||||
height: 0.2rem;
|
height: 0.2rem;
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.scrollProgressBar.color.background};
|
theme.theme.component.scrollProgressBar.color.background};
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProgressBar = styled.div`
|
const ProgressBar = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.scrollProgressBar.color.foreground};
|
theme.theme.component.scrollProgressBar.color.foreground};
|
||||||
`
|
`
|
||||||
|
|
||||||
const st = "scrollTop"
|
const st = "scrollTop"
|
||||||
|
@ -20,40 +20,42 @@ const h = document.documentElement
|
||||||
const b = document.body
|
const b = document.body
|
||||||
|
|
||||||
const ReadProgress = () => {
|
const ReadProgress = () => {
|
||||||
const [scroll, setScroll] = useState(0)
|
const [scroll, setScroll] = useState(0)
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
// https://stackoverflow.com/a/8028584/12979111
|
// https://stackoverflow.com/a/8028584/12979111
|
||||||
const scrollHandler = useCallback(() => {
|
const scrollHandler = useCallback(() => {
|
||||||
setScroll(((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100)
|
setScroll(
|
||||||
}, [])
|
((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
scrollHandler()
|
scrollHandler()
|
||||||
})
|
})
|
||||||
|
|
||||||
resizeObserver.observe(document.body)
|
resizeObserver.observe(document.body)
|
||||||
window.addEventListener("scroll", scrollHandler)
|
window.addEventListener("scroll", scrollHandler)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resizeObserver.disconnect()
|
resizeObserver.disconnect()
|
||||||
window.removeEventListener("scroll", scrollHandler)
|
window.removeEventListener("scroll", scrollHandler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// update on path change
|
// update on path change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollHandler()
|
scrollHandler()
|
||||||
}, 100)
|
}, 100)
|
||||||
}, [location])
|
}, [location])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Background>
|
<Background>
|
||||||
<ProgressBar style={{ width: `${scroll}%` }} />
|
<ProgressBar style={{ width: `${scroll}%` }} />
|
||||||
</Background>
|
</Background>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReadProgress
|
export default ReadProgress
|
||||||
|
|
|
@ -7,133 +7,133 @@ import styled from "styled-components"
|
||||||
import MainContent from "./MainContent"
|
import MainContent from "./MainContent"
|
||||||
|
|
||||||
const StyledContainer = styled(MainContent)`
|
const StyledContainer = styled(MainContent)`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
animation: fadein 2s;
|
animation: fadein 2s;
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledSVG = styled.svg`
|
const StyledSVG = styled.svg`
|
||||||
--color: ${({ theme }) => theme.theme.color.text.default};
|
--color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
margin-bottom: 4.5rem;
|
margin-bottom: 4.5rem;
|
||||||
|
|
||||||
transform: scale(2);
|
transform: scale(2);
|
||||||
|
|
||||||
#teabag {
|
#teabag {
|
||||||
transform-origin: top center;
|
transform-origin: top center;
|
||||||
transform: rotate(3deg);
|
transform: rotate(3deg);
|
||||||
animation: swingAnimation 2s infinite;
|
animation: swingAnimation 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#steamL {
|
#steamL {
|
||||||
stroke-dasharray: 13;
|
stroke-dasharray: 13;
|
||||||
stroke-dashoffset: 13;
|
stroke-dashoffset: 13;
|
||||||
animation: steamLargeAnimation 2s infinite;
|
animation: steamLargeAnimation 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#steamR {
|
#steamR {
|
||||||
stroke-dasharray: 9;
|
stroke-dasharray: 9;
|
||||||
stroke-dashoffset: 9;
|
stroke-dashoffset: 9;
|
||||||
animation: steamSmallAnimation 2s infinite;
|
animation: steamSmallAnimation 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes swingAnimation {
|
@keyframes swingAnimation {
|
||||||
50% {
|
50% {
|
||||||
transform: rotate(-3deg);
|
transform: rotate(-3deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes steamLargeAnimation {
|
@keyframes steamLargeAnimation {
|
||||||
0% {
|
0% {
|
||||||
stroke-dashoffset: 13;
|
stroke-dashoffset: 13;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
stroke-dashoffset: 39;
|
stroke-dashoffset: 39;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes steamSmallAnimation {
|
@keyframes steamSmallAnimation {
|
||||||
10% {
|
10% {
|
||||||
stroke-dashoffset: 9;
|
stroke-dashoffset: 9;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
80% {
|
80% {
|
||||||
stroke-dashoffset: 27;
|
stroke-dashoffset: 27;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
stroke-dashoffset: 27;
|
stroke-dashoffset: 27;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const Loading = () => {
|
const Loading = () => {
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledSVG
|
<StyledSVG
|
||||||
width="37"
|
width="37"
|
||||||
height="48"
|
height="48"
|
||||||
viewBox="0 0 37 48"
|
viewBox="0 0 37 48"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M27.0819 17H3.02508C1.91076 17 1.01376 17.9059 1.0485 19.0197C1.15761 22.5177 1.49703 29.7374 2.5 34C4.07125 40.6778 7.18553 44.8868 8.44856 46.3845C8.79051 46.79 9.29799 47 9.82843 47H20.0218C20.639 47 21.2193 46.7159 21.5659 46.2052C22.6765 44.5687 25.2312 40.4282 27.5 34C28.9757 29.8188 29.084 22.4043 29.0441 18.9156C29.0319 17.8436 28.1539 17 27.0819 17Z"
|
d="M27.0819 17H3.02508C1.91076 17 1.01376 17.9059 1.0485 19.0197C1.15761 22.5177 1.49703 29.7374 2.5 34C4.07125 40.6778 7.18553 44.8868 8.44856 46.3845C8.79051 46.79 9.29799 47 9.82843 47H20.0218C20.639 47 21.2193 46.7159 21.5659 46.2052C22.6765 44.5687 25.2312 40.4282 27.5 34C28.9757 29.8188 29.084 22.4043 29.0441 18.9156C29.0319 17.8436 28.1539 17 27.0819 17Z"
|
||||||
stroke="var(--color)"
|
stroke="var(--color)"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M29 23.5C29 23.5 34.5 20.5 35.5 25.4999C36.0986 28.4926 34.2033 31.5383 32 32.8713C29.4555 34.4108 28 34 28 34"
|
d="M29 23.5C29 23.5 34.5 20.5 35.5 25.4999C36.0986 28.4926 34.2033 31.5383 32 32.8713C29.4555 34.4108 28 34 28 34"
|
||||||
stroke="var(--color)"
|
stroke="var(--color)"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
id="teabag"
|
id="teabag"
|
||||||
fill="var(--color)"
|
fill="var(--color)"
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M16 25V17H14V25H12C10.3431 25 9 26.3431 9 28V34C9 35.6569 10.3431 37 12 37H18C19.6569 37 21 35.6569 21 34V28C21 26.3431 19.6569 25 18 25H16ZM11 28C11 27.4477 11.4477 27 12 27H18C18.5523 27 19 27.4477 19 28V34C19 34.5523 18.5523 35 18 35H12C11.4477 35 11 34.5523 11 34V28Z"
|
d="M16 25V17H14V25H12C10.3431 25 9 26.3431 9 28V34C9 35.6569 10.3431 37 12 37H18C19.6569 37 21 35.6569 21 34V28C21 26.3431 19.6569 25 18 25H16ZM11 28C11 27.4477 11.4477 27 12 27H18C18.5523 27 19 27.4477 19 28V34C19 34.5523 18.5523 35 18 35H12C11.4477 35 11 34.5523 11 34V28Z"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
id="steamL"
|
id="steamL"
|
||||||
d="M17 1C17 1 17 4.5 14 6.5C11 8.5 11 12 11 12"
|
d="M17 1C17 1 17 4.5 14 6.5C11 8.5 11 12 11 12"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke="var(--color)"
|
stroke="var(--color)"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
id="steamR"
|
id="steamR"
|
||||||
d="M21 6C21 6 21 8.22727 19 9.5C17 10.7727 17 13 17 13"
|
d="M21 6C21 6 21 8.22727 19 9.5C17 10.7727 17 13 17 13"
|
||||||
stroke="var(--color)"
|
stroke="var(--color)"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
</StyledSVG>
|
</StyledSVG>
|
||||||
<h2>Loading...</h2>
|
<h2>Loading...</h2>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Loading
|
export default Loading
|
||||||
|
|
|
@ -3,26 +3,26 @@ import styled, { css } from "styled-components"
|
||||||
import Card from "./Card"
|
import Card from "./Card"
|
||||||
|
|
||||||
export const mainContentCSS = css`
|
export const mainContentCSS = css`
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
table img {
|
table img {
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: ${({ theme }) =>
|
@media screen and (max-width: ${({ theme }) =>
|
||||||
theme.theme.maxDisplayWidth.mobile}) {
|
theme.theme.maxDisplayWidth.mobile}) {
|
||||||
width: auto;
|
width: auto;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const MainContent = styled(Card)`
|
const MainContent = styled(Card)`
|
||||||
${mainContentCSS}
|
${mainContentCSS}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default MainContent
|
export default MainContent
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { PostData } from "@developomp-site/blog-content/src/types/types"
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import {
|
import {
|
||||||
faBook,
|
faBook,
|
||||||
faCalendar,
|
faCalendar,
|
||||||
faHourglass,
|
faHourglass,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
import Tag from "./Tag"
|
import Tag from "./Tag"
|
||||||
|
@ -15,85 +15,85 @@ import TagList from "./TagList"
|
||||||
import MainContent from "./MainContent"
|
import MainContent from "./MainContent"
|
||||||
|
|
||||||
const PostCard = styled(MainContent)`
|
const PostCard = styled(MainContent)`
|
||||||
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: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 4px 10px
|
box-shadow: 0 4px 10px
|
||||||
${({ theme }) => theme.theme.component.card.color.hoverGlow};
|
${({ theme }) => theme.theme.component.card.color.hoverGlow};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const PostCardContainer = styled(Link)`
|
const PostCardContainer = styled(Link)`
|
||||||
display: block;
|
display: block;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
/* override link color */
|
/* override link color */
|
||||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const Title = styled.h1`
|
const Title = styled.h1`
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-style: bold;
|
font-style: bold;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
const MetaContainer = styled.small``
|
const MetaContainer = styled.small``
|
||||||
|
|
||||||
interface PostCardData extends PostData {
|
interface PostCardData extends PostData {
|
||||||
content_id: string
|
content_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
postData: PostCardData
|
postData: PostCardData
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
const { postData } = props
|
const { postData } = props
|
||||||
const { content_id, wordCount, date, readTime, title, tags } = postData
|
const { content_id, wordCount, date, readTime, title, tags } = postData
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostCard>
|
<PostCard>
|
||||||
<PostCardContainer to={content_id}>
|
<PostCardContainer to={content_id}>
|
||||||
<Title>
|
<Title>
|
||||||
{title || "No title"}
|
{title || "No title"}
|
||||||
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
||||||
{/\/series\/[^/]*$/.test(content_id) && " (series)"}
|
{/\/series\/[^/]*$/.test(content_id) && " (series)"}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<MetaContainer>
|
<MetaContainer>
|
||||||
<TagList direction="left">
|
<TagList direction="left">
|
||||||
{tags &&
|
{tags &&
|
||||||
tags.map((tag) => {
|
tags.map((tag) => {
|
||||||
return <Tag key={title + tag} text={tag} />
|
return <Tag key={title + tag} text={tag} />
|
||||||
})}
|
})}
|
||||||
</TagList>
|
</TagList>
|
||||||
<hr />
|
<hr />
|
||||||
<FontAwesomeIcon icon={faCalendar} />
|
<FontAwesomeIcon icon={faCalendar} />
|
||||||
|
|
||||||
{date || "Unknown date"}
|
{date || "Unknown date"}
|
||||||
|
|
||||||
<FontAwesomeIcon icon={faHourglass} />
|
<FontAwesomeIcon icon={faHourglass} />
|
||||||
|
|
||||||
{readTime ? readTime + " read" : "unknown read time"}
|
{readTime ? readTime + " read" : "unknown read time"}
|
||||||
|
|
||||||
<FontAwesomeIcon icon={faBook} />
|
<FontAwesomeIcon icon={faBook} />
|
||||||
|
|
||||||
{typeof wordCount === "number"
|
{typeof wordCount === "number"
|
||||||
? wordCount + " words"
|
? wordCount + " words"
|
||||||
: "unknown length"}
|
: "unknown length"}
|
||||||
</MetaContainer>
|
</MetaContainer>
|
||||||
</PostCardContainer>
|
</PostCardContainer>
|
||||||
</PostCard>
|
</PostCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,99 +12,112 @@ import NavbarData from "../../data/NavbarData"
|
||||||
import { HeaderButtonCSS } from "../Header/HeaderButton"
|
import { HeaderButtonCSS } from "../Header/HeaderButton"
|
||||||
|
|
||||||
const SidebarOpenButton = styled.div`
|
const SidebarOpenButton = styled.div`
|
||||||
${HeaderButtonCSS}
|
${HeaderButtonCSS}
|
||||||
|
|
||||||
@media only screen and (min-width: ${({ theme }) =>
|
@media only screen and (min-width: ${({ theme }) =>
|
||||||
theme.theme.maxDisplayWidth.mobile}) {
|
theme.theme.maxDisplayWidth.mobile}) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SidebarCloseButton = styled.div`
|
const SidebarCloseButton = styled.div`
|
||||||
${HeaderButtonCSS}
|
${HeaderButtonCSS}
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
margin-top: 0.2rem;
|
margin-top: 0.2rem;
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledOverlay = styled.div<{ isSidebarOpen: boolean }>`
|
const StyledOverlay = styled.div<{ isSidebarOpen: boolean }>`
|
||||||
display: ${(props) => (props.isSidebarOpen ? "block" : "none")};
|
display: ${(props) => (props.isSidebarOpen ? "block" : "none")};
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
background-color: rgba(0, 0, 0, 25%);
|
background-color: rgba(0, 0, 0, 25%);
|
||||||
|
|
||||||
* {
|
* {
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SidebarNav = styled.nav<{ isSidebarOpen: boolean }>`
|
const SidebarNav = styled.nav<{ isSidebarOpen: boolean }>`
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: ${(props) => (props.isSidebarOpen ? "0" : "-100%")};
|
right: ${(props) => (props.isSidebarOpen ? "0" : "-100%")};
|
||||||
transition: 350ms;
|
transition: 350ms;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.header.color.background};
|
theme.theme.component.header.color.background};
|
||||||
color: ${({ theme }) => theme.theme.component.header.color.text};
|
color: ${({ theme }) => theme.theme.component.header.color.text};
|
||||||
`
|
`
|
||||||
|
|
||||||
const SidebarWrap = styled.div`
|
const SidebarWrap = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const [isSidebarOpen, setSidebarOpen] = useState(false)
|
const [isSidebarOpen, setSidebarOpen] = useState(false)
|
||||||
const toggleSidebar = useCallback(() => {
|
const toggleSidebar = useCallback(() => {
|
||||||
setSidebarOpen((prev) => !prev)
|
setSidebarOpen((prev) => !prev)
|
||||||
document.body.style.overflow = isSidebarOpen ? "" : "hidden"
|
document.body.style.overflow = isSidebarOpen ? "" : "hidden"
|
||||||
}, [isSidebarOpen])
|
}, [isSidebarOpen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledOverlay isSidebarOpen={isSidebarOpen} onClick={toggleSidebar} />
|
<StyledOverlay
|
||||||
|
isSidebarOpen={isSidebarOpen}
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
/>
|
||||||
|
|
||||||
<SidebarOpenButton data-tip data-for="sidebar" onClick={toggleSidebar}>
|
<SidebarOpenButton
|
||||||
<FontAwesomeIcon icon={faEllipsisV}></FontAwesomeIcon>
|
data-tip
|
||||||
{!isMobile && (
|
data-for="sidebar"
|
||||||
<ReactTooltip id="sidebar" type="dark" effect="solid">
|
onClick={toggleSidebar}
|
||||||
<span>open sidebar</span>
|
>
|
||||||
</ReactTooltip>
|
<FontAwesomeIcon icon={faEllipsisV}></FontAwesomeIcon>
|
||||||
)}
|
{!isMobile && (
|
||||||
</SidebarOpenButton>
|
<ReactTooltip id="sidebar" type="dark" effect="solid">
|
||||||
|
<span>open sidebar</span>
|
||||||
|
</ReactTooltip>
|
||||||
|
)}
|
||||||
|
</SidebarOpenButton>
|
||||||
|
|
||||||
<SidebarNav isSidebarOpen={isSidebarOpen}>
|
<SidebarNav isSidebarOpen={isSidebarOpen}>
|
||||||
<SidebarWrap>
|
<SidebarWrap>
|
||||||
{/* close sidebar button */}
|
{/* close sidebar button */}
|
||||||
|
|
||||||
<SidebarCloseButton onClick={toggleSidebar}>
|
<SidebarCloseButton onClick={toggleSidebar}>
|
||||||
<FontAwesomeIcon icon={faTimes}></FontAwesomeIcon>Close
|
<FontAwesomeIcon icon={faTimes}></FontAwesomeIcon>Close
|
||||||
</SidebarCloseButton>
|
</SidebarCloseButton>
|
||||||
|
|
||||||
{/* sidebar items */}
|
{/* sidebar items */}
|
||||||
|
|
||||||
{NavbarData.map((item, index) => {
|
{NavbarData.map((item, index) => {
|
||||||
return <SubMenu onClick={toggleSidebar} item={item} key={index} />
|
return (
|
||||||
})}
|
<SubMenu
|
||||||
</SidebarWrap>
|
onClick={toggleSidebar}
|
||||||
</SidebarNav>
|
item={item}
|
||||||
</>
|
key={index}
|
||||||
)
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SidebarWrap>
|
||||||
|
</SidebarNav>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Sidebar
|
export default Sidebar
|
||||||
|
|
|
@ -11,70 +11,74 @@ import styled, { css } from "styled-components"
|
||||||
import button from "../../styles/button"
|
import button from "../../styles/button"
|
||||||
|
|
||||||
const sharedStyle = css`
|
const sharedStyle = css`
|
||||||
${button};
|
${button};
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
scale: 1.5;
|
scale: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SidebarLink = styled(Link)`
|
const SidebarLink = styled(Link)`
|
||||||
${sharedStyle}
|
${sharedStyle}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SidebarAnchor = styled.a`
|
const SidebarAnchor = styled.a`
|
||||||
${sharedStyle}
|
${sharedStyle}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SidebarLabel = styled.span`
|
const SidebarLabel = styled.span`
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: Item
|
item: Item
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubMenu = ({ item, onClick }: Props) => {
|
const SubMenu = ({ item, onClick }: Props) => {
|
||||||
const { path, icon, title } = item
|
const { path, icon, title } = item
|
||||||
const [isSubNavOpen, setSubNavOpen] = useState(false)
|
const [isSubNavOpen, setSubNavOpen] = useState(false)
|
||||||
const handleSidebarLinkClick = useCallback(() => {
|
const handleSidebarLinkClick = useCallback(() => {
|
||||||
onClick()
|
onClick()
|
||||||
setSubNavOpen((prev) => !prev)
|
setSubNavOpen((prev) => !prev)
|
||||||
}, [isSubNavOpen])
|
}, [isSubNavOpen])
|
||||||
|
|
||||||
if (path.at(0) == "/") {
|
if (path.at(0) == "/") {
|
||||||
return (
|
return (
|
||||||
<SidebarLink to={path} onClick={handleSidebarLinkClick}>
|
<SidebarLink to={path} onClick={handleSidebarLinkClick}>
|
||||||
<div>
|
<div>
|
||||||
{icon}
|
{icon}
|
||||||
<SidebarLabel>{title}</SidebarLabel>
|
<SidebarLabel>{title}</SidebarLabel>
|
||||||
</div>
|
</div>
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarAnchor target="_blank" href={path} onClick={handleSidebarLinkClick}>
|
<SidebarAnchor
|
||||||
<div>
|
target="_blank"
|
||||||
{icon}
|
href={path}
|
||||||
<SidebarLabel>{title}</SidebarLabel>
|
onClick={handleSidebarLinkClick}
|
||||||
</div>
|
>
|
||||||
</SidebarAnchor>
|
<div>
|
||||||
)
|
{icon}
|
||||||
|
<SidebarLabel>{title}</SidebarLabel>
|
||||||
|
</div>
|
||||||
|
</SidebarAnchor>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SubMenu
|
export default SubMenu
|
||||||
|
|
|
@ -5,23 +5,23 @@ import { faHashtag } from "@fortawesome/free-solid-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
|
|
||||||
const Tag = styled.div`
|
const Tag = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
margin-right: 0.8rem;
|
margin-right: 0.8rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||||
`
|
`
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
text: string
|
text: string
|
||||||
onClick?: (event: MouseEvent<never>) => void
|
onClick?: (event: MouseEvent<never>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Tag onClick={props.onClick || undefined}>
|
<Tag onClick={props.onClick || undefined}>
|
||||||
<FontAwesomeIcon icon={faHashtag} /> {props.text}
|
<FontAwesomeIcon icon={faHashtag} /> {props.text}
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,25 @@ import { ReactNode } from "react"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
const StyledTagList = styled.div<{ direction: string }>`
|
const StyledTagList = styled.div<{ direction: string }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
row-gap: 0.5rem;
|
row-gap: 0.5rem;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: ${({ direction }) => direction};
|
justify-content: ${({ direction }) => direction};
|
||||||
`
|
`
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
direction?: string
|
direction?: string
|
||||||
children?: ReactNode | undefined
|
children?: ReactNode | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagList = (props: Props) => {
|
const TagList = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<StyledTagList direction={props.direction || "center"}>
|
<StyledTagList direction={props.direction || "center"}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</StyledTagList>
|
</StyledTagList>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagList
|
export default TagList
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import {
|
import {
|
||||||
faHome,
|
faHome,
|
||||||
faFileLines,
|
faFileLines,
|
||||||
faUser,
|
faUser,
|
||||||
faUserTie,
|
faUserTie,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
// item from sidebar data
|
// item from sidebar data
|
||||||
export type Item = {
|
export type Item = {
|
||||||
path: string
|
path: string
|
||||||
icon: JSX.Element
|
icon: JSX.Element
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavbarData: Item[] = [
|
const NavbarData: Item[] = [
|
||||||
{
|
{
|
||||||
title: "Home",
|
title: "Home",
|
||||||
path: "/",
|
path: "/",
|
||||||
icon: <FontAwesomeIcon icon={faHome} />,
|
icon: <FontAwesomeIcon icon={faHome} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "About",
|
title: "About",
|
||||||
path: "https://developomp.com",
|
path: "https://developomp.com",
|
||||||
icon: <FontAwesomeIcon icon={faUser} />,
|
icon: <FontAwesomeIcon icon={faUser} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Portfolio",
|
title: "Portfolio",
|
||||||
path: "https://portfolio.developomp.com",
|
path: "https://portfolio.developomp.com",
|
||||||
icon: <FontAwesomeIcon icon={faFileLines} />,
|
icon: <FontAwesomeIcon icon={faFileLines} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Resume",
|
title: "Resume",
|
||||||
path: "/resume",
|
path: "/resume",
|
||||||
icon: <FontAwesomeIcon icon={faUserTie} />,
|
icon: <FontAwesomeIcon icon={faUserTie} />,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default NavbarData
|
export default NavbarData
|
||||||
|
|
|
@ -9,61 +9,61 @@ import storage from "local-storage-fallback"
|
||||||
export type SiteTheme = "dark" | "light"
|
export type SiteTheme = "dark" | "light"
|
||||||
|
|
||||||
export enum ActionsEnum {
|
export enum ActionsEnum {
|
||||||
UPDATE_THEME,
|
UPDATE_THEME,
|
||||||
UPDATE_LOCALE,
|
UPDATE_LOCALE,
|
||||||
}
|
}
|
||||||
|
|
||||||
// union of all actions
|
// union of all actions
|
||||||
export type GlobalAction = {
|
export type GlobalAction = {
|
||||||
type: ActionsEnum.UPDATE_THEME
|
type: ActionsEnum.UPDATE_THEME
|
||||||
payload: SiteTheme
|
payload: SiteTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGlobalState {
|
export interface IGlobalState {
|
||||||
currentTheme: SiteTheme
|
currentTheme: SiteTheme
|
||||||
theme: Theme
|
theme: Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGlobalContext {
|
export interface IGlobalContext {
|
||||||
globalState: IGlobalState
|
globalState: IGlobalState
|
||||||
dispatch: Dispatch<GlobalAction>
|
dispatch: Dispatch<GlobalAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: IGlobalState = {
|
const defaultState: IGlobalState = {
|
||||||
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
|
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
|
||||||
theme:
|
theme:
|
||||||
((storage.getItem("theme") || "dark") as SiteTheme) === "dark"
|
((storage.getItem("theme") || "dark") as SiteTheme) === "dark"
|
||||||
? darkTheme
|
? darkTheme
|
||||||
: lightTheme,
|
: lightTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globalContext = createContext({} as IGlobalContext)
|
export const globalContext = createContext({} as IGlobalContext)
|
||||||
|
|
||||||
function reducer(state = defaultState, action: GlobalAction): IGlobalState {
|
function reducer(state = defaultState, action: GlobalAction): IGlobalState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionsEnum.UPDATE_THEME:
|
case ActionsEnum.UPDATE_THEME:
|
||||||
state.currentTheme = action.payload
|
state.currentTheme = action.payload
|
||||||
state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme
|
state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...state }
|
return { ...state }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GlobalStore(props: { children: ReactNode }): ReactElement {
|
export function GlobalStore(props: { children: ReactNode }): ReactElement {
|
||||||
const [globalState, dispatch] = useReducer(reducer, defaultState)
|
const [globalState, dispatch] = useReducer(reducer, defaultState)
|
||||||
|
|
||||||
// save theme when it is changed
|
// save theme when it is changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
storage.setItem("theme", globalState.currentTheme)
|
storage.setItem("theme", globalState.currentTheme)
|
||||||
}, [globalState.currentTheme])
|
}, [globalState.currentTheme])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<globalContext.Provider value={{ globalState, dispatch }}>
|
<globalContext.Provider value={{ globalState, dispatch }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</globalContext.Provider>
|
</globalContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@ import App from "./App"
|
||||||
const container = document.getElementById("root") as HTMLElement
|
const container = document.getElementById("root") as HTMLElement
|
||||||
const root = createRoot(container)
|
const root = createRoot(container)
|
||||||
root.render(
|
root.render(
|
||||||
<GlobalStore>
|
<GlobalStore>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<App />
|
<App />
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</GlobalStore>
|
</GlobalStore>
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,75 +13,75 @@ import ShowMoreButton from "./ShowMoreButton"
|
||||||
import contentMap from "../../contentMap"
|
import contentMap from "../../contentMap"
|
||||||
|
|
||||||
const PostList = styled.div`
|
const PostList = styled.div`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
color: ${({ theme }) => theme.theme.color.text.default};
|
color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [howMany, setHowMany] = useState(5)
|
const [howMany, setHowMany] = useState(5)
|
||||||
const [postsLength, setPostsLength] = useState(0)
|
const [postsLength, setPostsLength] = useState(0)
|
||||||
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
||||||
|
|
||||||
const loadPostCards = useCallback(() => {
|
const loadPostCards = useCallback(() => {
|
||||||
let postCount = 0
|
let postCount = 0
|
||||||
const postCards = [] as JSX.Element[]
|
const postCards = [] as JSX.Element[]
|
||||||
|
|
||||||
for (const date of Object.keys(contentMap.date).reverse()) {
|
for (const date of Object.keys(contentMap.date).reverse()) {
|
||||||
if (postCount >= howMany) break
|
if (postCount >= howMany) break
|
||||||
|
|
||||||
const length = contentMap.date[date].length
|
const length = contentMap.date[date].length
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
if (postCount >= howMany) break
|
if (postCount >= howMany) break
|
||||||
|
|
||||||
postCount++
|
postCount++
|
||||||
const content_id = contentMap.date[date][length - i - 1]
|
const content_id = contentMap.date[date][length - i - 1]
|
||||||
|
|
||||||
postCards.push(
|
postCards.push(
|
||||||
<PostCard
|
<PostCard
|
||||||
key={content_id}
|
key={content_id}
|
||||||
postData={{
|
postData={{
|
||||||
content_id: content_id,
|
content_id: content_id,
|
||||||
...contentMap.posts[content_id],
|
...contentMap.posts[content_id],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPostCards(postCards)
|
setPostCards(postCards)
|
||||||
}, [howMany, postCards])
|
}, [howMany, postCards])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPostCards()
|
loadPostCards()
|
||||||
setPostsLength(Object.keys(contentMap.posts).length)
|
setPostsLength(Object.keys(contentMap.posts).length)
|
||||||
}, [howMany])
|
}, [howMany])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | Home</title>
|
<title>pomp | Home</title>
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:image" content="/icon/icon.svg" />
|
<meta property="og:image" content="/icon/icon.svg" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<PostList>
|
<PostList>
|
||||||
<h1>Recent Posts</h1>
|
<h1>Recent Posts</h1>
|
||||||
|
|
||||||
{postCards}
|
{postCards}
|
||||||
|
|
||||||
{postsLength > howMany && (
|
{postsLength > howMany && (
|
||||||
<ShowMoreButton
|
<ShowMoreButton
|
||||||
action={() => {
|
action={() => {
|
||||||
setHowMany((prev) => prev + 5)
|
setHowMany((prev) => prev + 5)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</PostList>
|
</PostList>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,16 @@ import styled from "styled-components"
|
||||||
import buttonStyle from "../../styles/button"
|
import buttonStyle from "../../styles/button"
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
${buttonStyle}
|
${buttonStyle}
|
||||||
|
|
||||||
/* center div */
|
/* center div */
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
`
|
`
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
action(): void
|
action(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
return <Button onClick={props.action}>Show more posts</Button>
|
return <Button onClick={props.action}>Show more posts</Button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,36 +4,36 @@ import { Helmet } from "react-helmet-async"
|
||||||
import MainContent from "../components/MainContent"
|
import MainContent from "../components/MainContent"
|
||||||
|
|
||||||
const StyledNotFound = styled(MainContent)`
|
const StyledNotFound = styled(MainContent)`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Styled404 = styled.h1`
|
const Styled404 = styled.h1`
|
||||||
font-size: 5rem;
|
font-size: 5rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | 404</title>
|
<title>pomp | 404</title>
|
||||||
|
|
||||||
<meta property="og:title" content="Page Not Found" />
|
<meta property="og:title" content="Page Not Found" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="http://blog.developomp.com" />
|
<meta property="og:url" content="http://blog.developomp.com" />
|
||||||
<meta
|
<meta
|
||||||
property="og:image"
|
property="og:image"
|
||||||
content="http://blog.developomp.com/icon/icon.svg"
|
content="http://blog.developomp.com/icon/icon.svg"
|
||||||
/>
|
/>
|
||||||
<meta property="og:description" content="Page does not exist" />
|
<meta property="og:description" content="Page does not exist" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<StyledNotFound>
|
<StyledNotFound>
|
||||||
<Styled404>404</Styled404>
|
<Styled404>404</Styled404>
|
||||||
<br />
|
<br />
|
||||||
Page was not found :(
|
Page was not found :(
|
||||||
</StyledNotFound>
|
</StyledNotFound>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NotFound
|
export default NotFound
|
||||||
|
|
|
@ -1,52 +1,53 @@
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import {
|
import {
|
||||||
faBook,
|
faBook,
|
||||||
faCalendar,
|
faCalendar,
|
||||||
faFile,
|
faFile,
|
||||||
faHourglass,
|
faHourglass,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
import { PageData } from "@developomp-site/blog-content/src/types/types"
|
import { PageData } from "@developomp-site/blog-content/src/types/types"
|
||||||
|
|
||||||
const StyledMetaContainer = styled.div`
|
const StyledMetaContainer = styled.div`
|
||||||
color: ${({ theme }) => theme.theme.color.text.gray};
|
color: ${({ theme }) => theme.theme.color.text.gray};
|
||||||
`
|
`
|
||||||
|
|
||||||
const Meta = (props: { fetchedPage: PageData }) => {
|
const Meta = (props: { fetchedPage: PageData }) => {
|
||||||
return (
|
return (
|
||||||
<StyledMetaContainer>
|
<StyledMetaContainer>
|
||||||
{/* posts count */}
|
{/* posts count */}
|
||||||
{props.fetchedPage.length > 0 && (
|
{props.fetchedPage.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon icon={faFile} />
|
<FontAwesomeIcon icon={faFile} />
|
||||||
|
|
||||||
{props.fetchedPage.length} post
|
{props.fetchedPage.length} post
|
||||||
{props.fetchedPage.length > 1 && "s"}
|
{props.fetchedPage.length > 1 && "s"}{" "}
|
||||||
</>
|
|
||||||
)}
|
</>
|
||||||
{/* date */}
|
)}
|
||||||
<FontAwesomeIcon icon={faCalendar} />
|
{/* date */}
|
||||||
|
<FontAwesomeIcon icon={faCalendar} />
|
||||||
{props.fetchedPage.date || "Unknown date"}
|
|
||||||
|
{props.fetchedPage.date || "Unknown date"}
|
||||||
{/* read time */}
|
|
||||||
<FontAwesomeIcon icon={faHourglass} />
|
{/* read time */}
|
||||||
|
<FontAwesomeIcon icon={faHourglass} />
|
||||||
{props.fetchedPage.readTime
|
|
||||||
? props.fetchedPage.readTime + " read"
|
{props.fetchedPage.readTime
|
||||||
: "unknown length"}
|
? props.fetchedPage.readTime + " read"
|
||||||
|
: "unknown length"}
|
||||||
{/* word count */}
|
|
||||||
<FontAwesomeIcon icon={faBook} />
|
{/* word count */}
|
||||||
|
<FontAwesomeIcon icon={faBook} />
|
||||||
{props.fetchedPage.wordCount
|
|
||||||
? props.fetchedPage.wordCount +
|
{props.fetchedPage.wordCount
|
||||||
" word" +
|
? props.fetchedPage.wordCount +
|
||||||
(props.fetchedPage.wordCount > 1 && "s")
|
" word" +
|
||||||
: "unknown words"}
|
(props.fetchedPage.wordCount > 1 && "s")
|
||||||
</StyledMetaContainer>
|
: "unknown words"}
|
||||||
)
|
</StyledMetaContainer>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Meta
|
export default Meta
|
||||||
|
|
|
@ -12,10 +12,10 @@ import NotFound from "../NotFound"
|
||||||
|
|
||||||
import SeriesControlButtons from "./SeriesControlButtons"
|
import SeriesControlButtons from "./SeriesControlButtons"
|
||||||
import {
|
import {
|
||||||
categorizePageType,
|
categorizePageType,
|
||||||
fetchContent,
|
fetchContent,
|
||||||
PageType,
|
PageType,
|
||||||
parsePageData,
|
parsePageData,
|
||||||
} from "./helper"
|
} from "./helper"
|
||||||
import Meta from "./Meta"
|
import Meta from "./Meta"
|
||||||
import Toc from "./Toc"
|
import Toc from "./Toc"
|
||||||
|
@ -25,112 +25,114 @@ import type { PageData } from "@developomp-site/blog-content/src/types/types"
|
||||||
import contentMap from "../../contentMap"
|
import contentMap from "../../contentMap"
|
||||||
|
|
||||||
const StyledTitle = styled.h1`
|
const StyledTitle = styled.h1`
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
|
|
||||||
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
||||||
const [pageType, setPageType] = useState<PageType>(PageType.POST)
|
const [pageType, setPageType] = useState<PageType>(PageType.POST)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
// this code runs if either the url or the locale changes
|
// this code runs if either the url or the locale changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const content_id = pathname.replace(/\/$/, "") // remove trailing slash
|
const content_id = pathname.replace(/\/$/, "") // remove trailing slash
|
||||||
const pageType = categorizePageType(content_id)
|
const pageType = categorizePageType(content_id)
|
||||||
|
|
||||||
fetchContent(pageType, content_id).then((fetched_content) => {
|
fetchContent(pageType, content_id).then((fetched_content) => {
|
||||||
if (!fetched_content) {
|
if (!fetched_content) {
|
||||||
// stop loading without fetching pageData so 404 page will display
|
// stop loading without fetching pageData so 404 page will display
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setPageData(parsePageData(fetched_content, pageType, content_id))
|
setPageData(parsePageData(fetched_content, pageType, content_id))
|
||||||
setPageType(pageType)
|
setPageType(pageType)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
if (isLoading) return <Loading />
|
if (isLoading) return <Loading />
|
||||||
|
|
||||||
if (!pageData) return <NotFound />
|
if (!pageData) return <NotFound />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | {pageData.title}</title>
|
<title>pomp | {pageData.title}</title>
|
||||||
|
|
||||||
<meta property="og:title" content={pageData.title} />
|
<meta property="og:title" content={pageData.title} />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:image" content="/icon/icon.svg" />
|
<meta property="og:image" content="/icon/icon.svg" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<MainContent>
|
<MainContent>
|
||||||
{/* next/previous series post buttons */}
|
{/* next/previous series post buttons */}
|
||||||
{pageType == PageType.SERIES && (
|
{pageType == PageType.SERIES && (
|
||||||
<SeriesControlButtons
|
<SeriesControlButtons
|
||||||
seriesHome={pageData.seriesHome}
|
seriesHome={pageData.seriesHome}
|
||||||
prevURL={pageData.prev}
|
prevURL={pageData.prev}
|
||||||
nextURL={pageData.next}
|
nextURL={pageData.next}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StyledTitle>{pageData.title}</StyledTitle>
|
<StyledTitle>{pageData.title}</StyledTitle>
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
{/* Post tags */}
|
{/* Post tags */}
|
||||||
{pageData.tags.length > 0 && (
|
{pageData.tags.length > 0 && (
|
||||||
<TagList direction="left">
|
<TagList direction="left">
|
||||||
{pageData.tags.map((tag) => {
|
{pageData.tags.map((tag) => {
|
||||||
return (
|
return (
|
||||||
<div key={pageData?.title + tag}>
|
<div key={pageData?.title + tag}>
|
||||||
<Tag text={tag} />
|
<Tag text={tag} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</TagList>
|
</TagList>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{/* Post metadata */}
|
{/* Post metadata */}
|
||||||
{[PageType.POST, PageType.SERIES, PageType.SERIES_HOME].includes(
|
{[
|
||||||
pageType
|
PageType.POST,
|
||||||
) && <Meta fetchedPage={pageData} />}
|
PageType.SERIES,
|
||||||
</small>
|
PageType.SERIES_HOME,
|
||||||
|
].includes(pageType) && <Meta fetchedPage={pageData} />}
|
||||||
|
</small>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{/* add table of contents if it exists */}
|
{/* add table of contents if it exists */}
|
||||||
<Toc data={pageData.toc} />
|
<Toc data={pageData.toc} />
|
||||||
|
|
||||||
{/* page content */}
|
{/* page content */}
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: pageData.content,
|
__html: pageData.content,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
|
|
||||||
{/* series post list */}
|
{/* series post list */}
|
||||||
|
|
||||||
{pageType == PageType.SERIES_HOME &&
|
{pageType == PageType.SERIES_HOME &&
|
||||||
pageData.order.map((post) => {
|
pageData.order.map((post) => {
|
||||||
return (
|
return (
|
||||||
<PostCard
|
<PostCard
|
||||||
key={post}
|
key={post}
|
||||||
postData={{
|
postData={{
|
||||||
content_id: post,
|
content_id: post,
|
||||||
...contentMap.posts[post],
|
...contentMap.posts[post],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,69 +3,69 @@ import { Link } from "react-router-dom"
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import {
|
import {
|
||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
faListUl,
|
faListUl,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
import buttonStyle from "../../styles/button"
|
import buttonStyle from "../../styles/button"
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Button = styled.div`
|
const Button = styled.div`
|
||||||
${buttonStyle}
|
${buttonStyle}
|
||||||
`
|
`
|
||||||
|
|
||||||
const DisabledButton = styled.div`
|
const DisabledButton = styled.div`
|
||||||
${buttonStyle}
|
${buttonStyle}
|
||||||
|
|
||||||
color: grey;
|
color: grey;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
`
|
`
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
seriesHome: string
|
seriesHome: string
|
||||||
prevURL?: string
|
prevURL?: string
|
||||||
nextURL?: string
|
nextURL?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{prevURL ? (
|
{prevURL ? (
|
||||||
<Link to={prevURL}>
|
<Link to={prevURL}>
|
||||||
<Button>
|
<Button>
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<DisabledButton>
|
<DisabledButton>
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
</DisabledButton>
|
</DisabledButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Link to={seriesHome}>
|
<Link to={seriesHome}>
|
||||||
<Button>
|
<Button>
|
||||||
<FontAwesomeIcon icon={faListUl} />
|
<FontAwesomeIcon icon={faListUl} />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{nextURL ? (
|
{nextURL ? (
|
||||||
<Link to={nextURL}>
|
<Link to={nextURL}>
|
||||||
<Button>
|
<Button>
|
||||||
<FontAwesomeIcon icon={faArrowRight} />
|
<FontAwesomeIcon icon={faArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<DisabledButton>
|
<DisabledButton>
|
||||||
<FontAwesomeIcon icon={faArrowRight} />
|
<FontAwesomeIcon icon={faArrowRight} />
|
||||||
</DisabledButton>
|
</DisabledButton>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SeriesControlButtons
|
export default SeriesControlButtons
|
||||||
|
|
|
@ -7,52 +7,54 @@ import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
const StyledTocToggleButton = styled.button`
|
const StyledTocToggleButton = styled.button`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
color: ${({ theme }) => theme.theme.color.text.highContrast};
|
color: ${({ theme }) => theme.theme.color.text.highContrast};
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledCollapseContainer = styled.div`
|
const StyledCollapseContainer = styled.div`
|
||||||
* {
|
* {
|
||||||
transition: height 200ms ease-out;
|
transition: height 200ms ease-out;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const Toc = (props: { data?: string }) => {
|
const Toc = (props: { data?: string }) => {
|
||||||
const [isTocOpened, setIsTocOpened] = useState(
|
const [isTocOpened, setIsTocOpened] = useState(
|
||||||
storage.getItem("isTocOpened") == "true"
|
storage.getItem("isTocOpened") == "true"
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
storage.setItem("isTocOpened", isTocOpened.toString())
|
storage.setItem("isTocOpened", isTocOpened.toString())
|
||||||
}, [isTocOpened])
|
}, [isTocOpened])
|
||||||
|
|
||||||
if (!props.data) return <></>
|
if (!props.data) return <></>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledTocToggleButton
|
<StyledTocToggleButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsTocOpened((prev) => !prev)
|
setIsTocOpened((prev) => !prev)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong>
|
<strong>
|
||||||
Table of Contents
|
Table of Contents
|
||||||
<FontAwesomeIcon icon={isTocOpened ? faCaretUp : faCaretDown} />
|
<FontAwesomeIcon
|
||||||
</strong>
|
icon={isTocOpened ? faCaretUp : faCaretDown}
|
||||||
</StyledTocToggleButton>
|
/>
|
||||||
<StyledCollapseContainer>
|
</strong>
|
||||||
<Collapse isOpened={isTocOpened}>
|
</StyledTocToggleButton>
|
||||||
<div dangerouslySetInnerHTML={{ __html: props.data }} />
|
<StyledCollapseContainer>
|
||||||
</Collapse>
|
<Collapse isOpened={isTocOpened}>
|
||||||
</StyledCollapseContainer>
|
<div dangerouslySetInnerHTML={{ __html: props.data }} />
|
||||||
<hr />
|
</Collapse>
|
||||||
</>
|
</StyledCollapseContainer>
|
||||||
)
|
<hr />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Toc
|
export default Toc
|
||||||
|
|
|
@ -5,163 +5,165 @@ import type { PageData } from "@developomp-site/blog-content/src/types/types"
|
||||||
import contentMap from "../../contentMap"
|
import contentMap from "../../contentMap"
|
||||||
|
|
||||||
export enum PageType {
|
export enum PageType {
|
||||||
POST,
|
POST,
|
||||||
SERIES,
|
SERIES,
|
||||||
SERIES_HOME,
|
SERIES_HOME,
|
||||||
PORTFOLIO_PROJECT,
|
PORTFOLIO_PROJECT,
|
||||||
UNSEARCHABLE,
|
UNSEARCHABLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchContent(pageType: PageType, url: string) {
|
export async function fetchContent(pageType: PageType, url: string) {
|
||||||
try {
|
try {
|
||||||
if (pageType == PageType.UNSEARCHABLE) {
|
if (pageType == PageType.UNSEARCHABLE) {
|
||||||
return await import(
|
return await import(
|
||||||
`@developomp-site/blog-content/dist/content/unsearchable${url}.json`
|
`@developomp-site/blog-content/dist/content/unsearchable${url}.json`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return await import(
|
return await import(
|
||||||
`@developomp-site/blog-content/dist/content${url}.json`
|
`@developomp-site/blog-content/dist/content${url}.json`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function categorizePageType(content_id: string): PageType {
|
export function categorizePageType(content_id: string): PageType {
|
||||||
if (content_id.startsWith("/post")) return PageType.POST
|
if (content_id.startsWith("/post")) return PageType.POST
|
||||||
if (content_id.startsWith("/portfolio")) return PageType.PORTFOLIO_PROJECT
|
if (content_id.startsWith("/portfolio")) return PageType.PORTFOLIO_PROJECT
|
||||||
if (content_id.startsWith("/series")) {
|
if (content_id.startsWith("/series")) {
|
||||||
// if the URL looks like /series/series-title (if the url has two slashes)
|
// if the URL looks like /series/series-title (if the url has two slashes)
|
||||||
if ([...(content_id.match(/\//g) || [])].length == 2)
|
if ([...(content_id.match(/\//g) || [])].length == 2)
|
||||||
return PageType.SERIES_HOME
|
return PageType.SERIES_HOME
|
||||||
|
|
||||||
// if the URL looks like /series/series-title/post-title (if the url does not have 2 slashes)
|
// if the URL looks like /series/series-title/post-title (if the url does not have 2 slashes)
|
||||||
return PageType.SERIES
|
return PageType.SERIES
|
||||||
}
|
}
|
||||||
|
|
||||||
return PageType.UNSEARCHABLE
|
return PageType.UNSEARCHABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parsePageData(
|
export function parsePageData(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
fetched_content: any,
|
fetched_content: any,
|
||||||
pageType: PageType,
|
pageType: PageType,
|
||||||
content_id: string
|
content_id: string
|
||||||
): PageData {
|
): PageData {
|
||||||
// page date to be saved as a react state
|
// page date to be saved as a react state
|
||||||
const pageData: PageData = {
|
const pageData: PageData = {
|
||||||
title: "No title",
|
title: "No title",
|
||||||
date: "Unknown date",
|
date: "Unknown date",
|
||||||
readTime: "Unknown read time",
|
readTime: "Unknown read time",
|
||||||
wordCount: 0,
|
wordCount: 0,
|
||||||
tags: [],
|
tags: [],
|
||||||
toc: undefined,
|
toc: undefined,
|
||||||
content: "No content",
|
content: "No content",
|
||||||
|
|
||||||
// series
|
// series
|
||||||
|
|
||||||
seriesHome: "",
|
seriesHome: "",
|
||||||
prev: "",
|
prev: "",
|
||||||
next: "",
|
next: "",
|
||||||
|
|
||||||
// series home
|
// series home
|
||||||
|
|
||||||
order: [],
|
order: [],
|
||||||
length: 0,
|
length: 0,
|
||||||
|
|
||||||
// portfolio
|
// portfolio
|
||||||
|
|
||||||
image: "",
|
image: "",
|
||||||
overview: "",
|
overview: "",
|
||||||
badges: [],
|
badges: [],
|
||||||
repo: "",
|
repo: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// load and parse content differently depending on the content type
|
// load and parse content differently depending on the content type
|
||||||
switch (pageType) {
|
switch (pageType) {
|
||||||
case PageType.POST: {
|
case PageType.POST: {
|
||||||
const post = contentMap.posts[content_id]
|
const post = contentMap.posts[content_id]
|
||||||
|
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
pageData.toc = fetched_content.toc
|
pageData.toc = fetched_content.toc
|
||||||
|
|
||||||
pageData.title = post.title
|
pageData.title = post.title
|
||||||
pageData.date = post.date
|
pageData.date = post.date
|
||||||
pageData.readTime = post.readTime
|
pageData.readTime = post.readTime
|
||||||
pageData.wordCount = post.wordCount
|
pageData.wordCount = post.wordCount
|
||||||
pageData.tags = post.tags || []
|
pageData.tags = post.tags || []
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case PageType.SERIES: {
|
case PageType.SERIES: {
|
||||||
const seriesURL = content_id.slice(0, content_id.lastIndexOf("/"))
|
const seriesURL = content_id.slice(0, content_id.lastIndexOf("/"))
|
||||||
|
|
||||||
const curr = contentMap.series[seriesURL].order.indexOf(content_id)
|
const curr = contentMap.series[seriesURL].order.indexOf(content_id)
|
||||||
const prev = curr - 1
|
const prev = curr - 1
|
||||||
const next = curr + 1
|
const next = curr + 1
|
||||||
|
|
||||||
const post = contentMap.posts[content_id]
|
const post = contentMap.posts[content_id]
|
||||||
|
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
pageData.toc = fetched_content.toc
|
pageData.toc = fetched_content.toc
|
||||||
|
|
||||||
pageData.title = post.title
|
pageData.title = post.title
|
||||||
pageData.date = post.date
|
pageData.date = post.date
|
||||||
pageData.readTime = post.readTime
|
pageData.readTime = post.readTime
|
||||||
pageData.wordCount = post.wordCount
|
pageData.wordCount = post.wordCount
|
||||||
pageData.tags = post.tags || []
|
pageData.tags = post.tags || []
|
||||||
|
|
||||||
pageData.seriesHome = seriesURL
|
pageData.seriesHome = seriesURL
|
||||||
pageData.prev =
|
pageData.prev =
|
||||||
prev >= 0 ? contentMap.series[seriesURL].order[prev] : undefined
|
prev >= 0 ? contentMap.series[seriesURL].order[prev] : undefined
|
||||||
pageData.next =
|
pageData.next =
|
||||||
next < contentMap.series[seriesURL].order.length
|
next < contentMap.series[seriesURL].order.length
|
||||||
? contentMap.series[seriesURL].order[next]
|
? contentMap.series[seriesURL].order[next]
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case PageType.SERIES_HOME: {
|
case PageType.SERIES_HOME: {
|
||||||
const seriesData = contentMap.series[content_id]
|
const seriesData = contentMap.series[content_id]
|
||||||
|
|
||||||
pageData.title = seriesData.title
|
pageData.title = seriesData.title
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
|
|
||||||
pageData.date = seriesData.date
|
pageData.date = seriesData.date
|
||||||
pageData.readTime = seriesData.readTime
|
pageData.readTime = seriesData.readTime
|
||||||
pageData.wordCount = seriesData.wordCount
|
pageData.wordCount = seriesData.wordCount
|
||||||
pageData.order = seriesData.order
|
pageData.order = seriesData.order
|
||||||
pageData.length = seriesData.length
|
pageData.length = seriesData.length
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case PageType.PORTFOLIO_PROJECT: {
|
case PageType.PORTFOLIO_PROJECT: {
|
||||||
const data =
|
const data =
|
||||||
portfolio.projects[content_id as keyof typeof portfolio.projects]
|
portfolio.projects[
|
||||||
|
content_id as keyof typeof portfolio.projects
|
||||||
|
]
|
||||||
|
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
pageData.toc = fetched_content.toc
|
pageData.toc = fetched_content.toc
|
||||||
|
|
||||||
pageData.title = data.name
|
pageData.title = data.name
|
||||||
pageData.image = data.image
|
pageData.image = data.image
|
||||||
pageData.overview = data.overview
|
pageData.overview = data.overview
|
||||||
pageData.badges = data.badges
|
pageData.badges = data.badges
|
||||||
pageData.repo = data.repo
|
pageData.repo = data.repo
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case PageType.UNSEARCHABLE: {
|
case PageType.UNSEARCHABLE: {
|
||||||
pageData.title = contentMap.unsearchable[content_id].title
|
pageData.title = contentMap.unsearchable[content_id].title
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageData
|
return pageData
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,27 +2,27 @@ import { DateRange } from "react-date-range"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
export const DateRangeControl = styled.div`
|
export const DateRangeControl = styled.div`
|
||||||
width: 350px;
|
width: 350px;
|
||||||
|
|
||||||
@media screen and (max-width: ${(props) =>
|
@media screen and (max-width: ${(props) =>
|
||||||
props.theme.theme.maxDisplayWidth.mobile}) {
|
props.theme.theme.maxDisplayWidth.mobile}) {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const ClearDateButton = styled.button`
|
export const ClearDateButton = styled.button`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
background-color: tomato; /* 🍅 mmm tomato 🍅 */
|
background-color: tomato; /* 🍅 mmm tomato 🍅 */
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const StyledDateRange = styled(DateRange)`
|
export const StyledDateRange = styled(DateRange)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
`
|
`
|
||||||
|
|
|
@ -24,251 +24,259 @@ import "react-date-range/dist/theme/default.css"
|
||||||
const searchIndex = elasticlunr.Index.load(searchData as never)
|
const searchIndex = elasticlunr.Index.load(searchData as never)
|
||||||
|
|
||||||
export interface SearchParams {
|
export interface SearchParams {
|
||||||
date_from: string
|
date_from: string
|
||||||
date_to: string
|
date_to: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
query: string
|
query: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultDateRange = [
|
const defaultDateRange = [
|
||||||
{
|
{
|
||||||
startDate: undefined,
|
startDate: undefined,
|
||||||
endDate: undefined,
|
endDate: undefined,
|
||||||
key: "selection",
|
key: "selection",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const StyledSearch = styled(MainContent)`
|
const StyledSearch = styled(MainContent)`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledSearchContainer = styled.div`
|
const StyledSearchContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
@media screen and (max-width: ${(props) =>
|
@media screen and (max-width: ${(props) =>
|
||||||
props.theme.theme.maxDisplayWidth.mobile}) {
|
props.theme.theme.maxDisplayWidth.mobile}) {
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledSearchControlContainer = styled.div`
|
const StyledSearchControlContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
|
|
||||||
@media screen and (max-width: ${(props) =>
|
@media screen and (max-width: ${(props) =>
|
||||||
props.theme.theme.maxDisplayWidth.mobile}) {
|
props.theme.theme.maxDisplayWidth.mobile}) {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// check if post date is withing the range
|
// check if post date is withing the range
|
||||||
function isDateInRange(dateStringToCompare: string, range: Range): boolean {
|
function isDateInRange(dateStringToCompare: string, range: Range): boolean {
|
||||||
if (!dateStringToCompare) throw Error("No date to compare")
|
if (!dateStringToCompare) throw Error("No date to compare")
|
||||||
const dateToCompare = new Date(dateStringToCompare)
|
const dateToCompare = new Date(dateStringToCompare)
|
||||||
const { startDate, endDate } = range
|
const { startDate, endDate } = range
|
||||||
|
|
||||||
const startDateExists = !!startDate
|
const startDateExists = !!startDate
|
||||||
const endDateExists = !!endDate
|
const endDateExists = !!endDate
|
||||||
|
|
||||||
if (endDateExists && !startDateExists) return dateToCompare < endDate
|
if (endDateExists && !startDateExists) return dateToCompare < endDate
|
||||||
if (startDateExists && !endDateExists) return dateToCompare > startDate
|
if (startDateExists && !endDateExists) return dateToCompare > startDate
|
||||||
if (startDateExists && endDateExists)
|
if (startDateExists && endDateExists)
|
||||||
return dateToCompare > startDate && dateToCompare < endDate
|
return dateToCompare > startDate && dateToCompare < endDate
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSelectedTagsInPost(selectedTags?: TagsData[], postTags?: string[]) {
|
function isSelectedTagsInPost(selectedTags?: TagsData[], postTags?: string[]) {
|
||||||
if (!selectedTags || selectedTags.length <= 0) return true
|
if (!selectedTags || selectedTags.length <= 0) return true
|
||||||
if (!postTags || postTags.length <= 0) return false
|
if (!postTags || postTags.length <= 0) return false
|
||||||
|
|
||||||
// if tag is empty or undefined
|
// if tag is empty or undefined
|
||||||
const tagValues = selectedTags.map((value) => value.value)
|
const tagValues = selectedTags.map((value) => value.value)
|
||||||
if (!postTags.every((val) => tagValues.includes(val))) return false
|
if (!postTags.every((val) => tagValues.includes(val))) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const Search = () => {
|
const Search = () => {
|
||||||
// URL search parameters
|
// URL search parameters
|
||||||
const [URLSearchParams, setURLSearchParams] = useSearchParams()
|
const [URLSearchParams, setURLSearchParams] = useSearchParams()
|
||||||
|
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
|
||||||
const [dateRange, setDateRange] = useState<Range[]>(defaultDateRange)
|
const [dateRange, setDateRange] = useState<Range[]>(defaultDateRange)
|
||||||
const [selectedTags, setSelectedTags] = useState<TagsData[]>([])
|
const [selectedTags, setSelectedTags] = useState<TagsData[]>([])
|
||||||
const [searchInput, setSearchInput] = useState("")
|
const [searchInput, setSearchInput] = useState("")
|
||||||
|
|
||||||
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
||||||
|
|
||||||
const doSearch = useCallback(() => {
|
const doSearch = useCallback(() => {
|
||||||
try {
|
try {
|
||||||
const _postCards: JSX.Element[] = []
|
const _postCards: JSX.Element[] = []
|
||||||
for (const res of searchIndex.search(searchInput)) {
|
for (const res of searchIndex.search(searchInput)) {
|
||||||
const postData = contentMap.posts[res.ref]
|
const postData = contentMap.posts[res.ref]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
postData && // if post data exists
|
postData && // if post data exists
|
||||||
isDateInRange(postData.date, dateRange[0]) && // date is within range
|
isDateInRange(postData.date, dateRange[0]) && // date is within range
|
||||||
isSelectedTagsInPost(selectedTags, postData.tags) // if post include tags
|
isSelectedTagsInPost(selectedTags, postData.tags) // if post include tags
|
||||||
) {
|
) {
|
||||||
_postCards.push(
|
_postCards.push(
|
||||||
<PostCard
|
<PostCard
|
||||||
key={res.ref}
|
key={res.ref}
|
||||||
postData={{
|
postData={{
|
||||||
content_id: res.ref,
|
content_id: res.ref,
|
||||||
...postData,
|
...postData,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply search result
|
// apply search result
|
||||||
setPostCards(_postCards)
|
setPostCards(_postCards)
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
}, [dateRange, selectedTags, searchInput])
|
}, [dateRange, selectedTags, searchInput])
|
||||||
|
|
||||||
// parse search parameters
|
// parse search parameters
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const [key, value] of URLSearchParams.entries()) {
|
for (const [key, value] of URLSearchParams.entries()) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "date_from":
|
case "date_from":
|
||||||
setDateRange((prev) => [{ ...prev[0], startDate: new Date(value) }])
|
setDateRange((prev) => [
|
||||||
break
|
{ ...prev[0], startDate: new Date(value) },
|
||||||
|
])
|
||||||
|
break
|
||||||
|
|
||||||
case "date_to":
|
case "date_to":
|
||||||
setDateRange((prev) => [{ ...prev[0], endDate: new Date(value) }])
|
setDateRange((prev) => [
|
||||||
break
|
{ ...prev[0], endDate: new Date(value) },
|
||||||
|
])
|
||||||
|
break
|
||||||
|
|
||||||
case "tags":
|
case "tags":
|
||||||
setSelectedTags(
|
setSelectedTags(
|
||||||
value.split(",").map((elem) => {
|
value.split(",").map((elem) => {
|
||||||
return { value: elem, label: elem }
|
return { value: elem, label: elem }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
case "query":
|
case "query":
|
||||||
setSearchInput(value)
|
setSearchInput(value)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// update URL when data changes
|
// update URL when data changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized) return
|
if (!initialized) return
|
||||||
|
|
||||||
let date_from
|
let date_from
|
||||||
let date_to
|
let date_to
|
||||||
|
|
||||||
// convert Date to YYYY-MM-DD string if it exists
|
// convert Date to YYYY-MM-DD string if it exists
|
||||||
if (dateRange[0].startDate)
|
if (dateRange[0].startDate)
|
||||||
date_from = dateRange[0].startDate.toISOString().split("T")[0]
|
date_from = dateRange[0].startDate.toISOString().split("T")[0]
|
||||||
|
|
||||||
if (dateRange[0].endDate)
|
if (dateRange[0].endDate)
|
||||||
date_to = dateRange[0].endDate.toISOString().split("T")[0]
|
date_to = dateRange[0].endDate.toISOString().split("T")[0]
|
||||||
|
|
||||||
setURLSearchParams({
|
setURLSearchParams({
|
||||||
...(date_from && {
|
...(date_from && {
|
||||||
date_from: date_from,
|
date_from: date_from,
|
||||||
}),
|
}),
|
||||||
...(date_to && {
|
...(date_to && {
|
||||||
date_to: date_to,
|
date_to: date_to,
|
||||||
}),
|
}),
|
||||||
...(selectedTags.length > 0 && {
|
...(selectedTags.length > 0 && {
|
||||||
tags: selectedTags.map((value) => value.value).join(","),
|
tags: selectedTags.map((value) => value.value).join(","),
|
||||||
}),
|
}),
|
||||||
...(searchInput && {
|
...(searchInput && {
|
||||||
query: searchInput,
|
query: searchInput,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}, [dateRange, selectedTags, searchInput])
|
}, [dateRange, selectedTags, searchInput])
|
||||||
|
|
||||||
// run search if date range and selected tags change
|
// run search if date range and selected tags change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
doSearch()
|
doSearch()
|
||||||
}, [dateRange, selectedTags])
|
}, [dateRange, selectedTags])
|
||||||
|
|
||||||
// run search if user stops typing
|
// run search if user stops typing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
doSearch()
|
doSearch()
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
return () => clearTimeout(delayDebounceFn)
|
return () => clearTimeout(delayDebounceFn)
|
||||||
}, [searchInput])
|
}, [searchInput])
|
||||||
|
|
||||||
if (!initialized) return <Loading />
|
if (!initialized) return <Loading />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | Search</title>
|
<title>pomp | Search</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<StyledSearch>
|
<StyledSearch>
|
||||||
<h1>Search</h1>
|
<h1>Search</h1>
|
||||||
|
|
||||||
<StyledSearchContainer>
|
<StyledSearchContainer>
|
||||||
<DateRangeControl>
|
<DateRangeControl>
|
||||||
<ClearDateButton
|
<ClearDateButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDateRange(defaultDateRange)
|
setDateRange(defaultDateRange)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reset date range
|
Reset date range
|
||||||
</ClearDateButton>
|
</ClearDateButton>
|
||||||
<StyledDateRange
|
<StyledDateRange
|
||||||
editableDateInputs
|
editableDateInputs
|
||||||
retainEndDateOnFirstSelection
|
retainEndDateOnFirstSelection
|
||||||
moveRangeOnFirstSelection={false}
|
moveRangeOnFirstSelection={false}
|
||||||
ranges={dateRange}
|
ranges={dateRange}
|
||||||
onChange={(rangesByKey) => {
|
onChange={(rangesByKey) => {
|
||||||
setDateRange([rangesByKey.selection])
|
setDateRange([rangesByKey.selection])
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DateRangeControl>
|
</DateRangeControl>
|
||||||
|
|
||||||
<StyledSearchControlContainer
|
<StyledSearchControlContainer
|
||||||
onSubmit={(event) => event.preventDefault()}
|
onSubmit={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
autoFocus
|
autoFocus
|
||||||
type="search"
|
type="search"
|
||||||
value={searchInput}
|
value={searchInput}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
onChange={(event) => setSearchInput(event.target.value)}
|
onChange={(event) =>
|
||||||
onKeyPress={(event) => {
|
setSearchInput(event.target.value)
|
||||||
event.key === "Enter" && searchInput && doSearch()
|
}
|
||||||
}}
|
onKeyPress={(event) => {
|
||||||
/>
|
event.key === "Enter" &&
|
||||||
{postCards.length} result{postCards.length > 1 && "s"}
|
searchInput &&
|
||||||
<TagSelect
|
doSearch()
|
||||||
defaultValue={selectedTags}
|
}}
|
||||||
onChange={(newValue) => {
|
/>
|
||||||
setSelectedTags(newValue as TagsData[])
|
{postCards.length} result{postCards.length > 1 && "s"}
|
||||||
}}
|
<TagSelect
|
||||||
/>
|
defaultValue={selectedTags}
|
||||||
</StyledSearchControlContainer>
|
onChange={(newValue) => {
|
||||||
</StyledSearchContainer>
|
setSelectedTags(newValue as TagsData[])
|
||||||
</StyledSearch>
|
}}
|
||||||
|
/>
|
||||||
|
</StyledSearchControlContainer>
|
||||||
|
</StyledSearchContainer>
|
||||||
|
</StyledSearch>
|
||||||
|
|
||||||
{postCards}
|
{postCards}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Search
|
export default Search
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
export default styled.input`
|
export default styled.input`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 100px; /* arbitrarily large value */
|
border-radius: 100px; /* arbitrarily large value */
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: ${({ theme }) => theme.theme.color.text.default};
|
color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
${(props) => props.theme.theme.component.input.color.border.default};
|
${(props) => props.theme.theme.component.input.color.border.default};
|
||||||
background-color: ${(props) =>
|
background-color: ${(props) =>
|
||||||
props.theme.theme.component.input.color.background.default};
|
props.theme.theme.component.input.color.background.default};
|
||||||
|
|
||||||
::placeholder {
|
::placeholder {
|
||||||
color: ${(props) => props.theme.theme.component.input.color.placeHolder};
|
color: ${(props) =>
|
||||||
opacity: 1;
|
props.theme.theme.component.input.color.placeHolder};
|
||||||
}
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
${({ theme }) => theme.theme.component.input.color.border.hover};
|
${({ theme }) => theme.theme.component.input.color.border.hover};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
${({ theme }) => theme.theme.component.input.color.border.focus};
|
${({ theme }) => theme.theme.component.input.color.border.focus};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -6,102 +6,108 @@ import contentMap from "../../contentMap"
|
||||||
import { globalContext } from "../../globalContext"
|
import { globalContext } from "../../globalContext"
|
||||||
|
|
||||||
const StyledReactTagsContainer = styled.div`
|
const StyledReactTagsContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
export interface TagsData {
|
export interface TagsData {
|
||||||
value: string
|
value: string
|
||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: TagsData[] = contentMap.meta.tags.map((elem) => ({
|
const options: TagsData[] = contentMap.meta.tags.map((elem) => ({
|
||||||
value: elem,
|
value: elem,
|
||||||
label: elem,
|
label: elem,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
interface TagSelectProps {
|
interface TagSelectProps {
|
||||||
defaultValue: TagsData[]
|
defaultValue: TagsData[]
|
||||||
onChange(newValue: unknown): void
|
onChange(newValue: unknown): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagSelect = (props: TagSelectProps) => {
|
const TagSelect = (props: TagSelectProps) => {
|
||||||
const { globalState } = useContext(globalContext)
|
const { globalState } = useContext(globalContext)
|
||||||
const { theme } = globalState
|
const { theme } = globalState
|
||||||
const { onChange, defaultValue: selectedTags } = props
|
const { onChange, defaultValue: selectedTags } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledReactTagsContainer>
|
<StyledReactTagsContainer>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Select tags..."
|
placeholder="Select tags..."
|
||||||
theme={(reactSelectTheme) => ({
|
theme={(reactSelectTheme) => ({
|
||||||
...reactSelectTheme,
|
...reactSelectTheme,
|
||||||
colors: {
|
colors: {
|
||||||
...reactSelectTheme.colors,
|
...reactSelectTheme.colors,
|
||||||
neutral0: theme.component.input.color.background.default,
|
neutral0:
|
||||||
neutral5: "hsl(0, 0%, 20%)",
|
theme.component.input.color.background.default,
|
||||||
neutral10: "hsl(0, 0%, 30%)",
|
neutral5: "hsl(0, 0%, 20%)",
|
||||||
neutral20: "hsl(0, 0%, 40%)",
|
neutral10: "hsl(0, 0%, 30%)",
|
||||||
neutral30: "hsl(0, 0%, 50%)",
|
neutral20: "hsl(0, 0%, 40%)",
|
||||||
neutral40: "hsl(0, 0%, 60%)",
|
neutral30: "hsl(0, 0%, 50%)",
|
||||||
neutral50: "hsl(0, 0%, 70%)",
|
neutral40: "hsl(0, 0%, 60%)",
|
||||||
neutral60: "hsl(0, 0%, 80%)",
|
neutral50: "hsl(0, 0%, 70%)",
|
||||||
neutral70: "hsl(0, 0%, 90%)",
|
neutral60: "hsl(0, 0%, 80%)",
|
||||||
neutral80: "hsl(0, 0%, 95%)",
|
neutral70: "hsl(0, 0%, 90%)",
|
||||||
neutral90: "hsl(0, 0%, 100%)",
|
neutral80: "hsl(0, 0%, 95%)",
|
||||||
primary25: "hotpink",
|
neutral90: "hsl(0, 0%, 100%)",
|
||||||
primary: "black",
|
primary25: "hotpink",
|
||||||
},
|
primary: "black",
|
||||||
})}
|
},
|
||||||
styles={{
|
})}
|
||||||
option: (styles) => ({
|
styles={{
|
||||||
...styles,
|
option: (styles) => ({
|
||||||
backgroundColor: theme.component.input.color.background.default,
|
...styles,
|
||||||
color: theme.color.text.default,
|
backgroundColor:
|
||||||
cursor: "pointer",
|
theme.component.input.color.background.default,
|
||||||
":hover": {
|
color: theme.color.text.default,
|
||||||
backgroundColor: theme.component.input.color.background.itemHover,
|
cursor: "pointer",
|
||||||
},
|
":hover": {
|
||||||
}),
|
backgroundColor:
|
||||||
control: (styles) => ({
|
theme.component.input.color.background
|
||||||
...styles,
|
.itemHover,
|
||||||
backgroundColor: theme.component.input.color.background.default,
|
},
|
||||||
border: `1px solid ${theme.component.input.color.border.default}`,
|
}),
|
||||||
":hover": {
|
control: (styles) => ({
|
||||||
border: `1px solid ${theme.component.input.color.border.hover}`,
|
...styles,
|
||||||
},
|
backgroundColor:
|
||||||
":focus": {
|
theme.component.input.color.background.default,
|
||||||
border: `1px solid ${theme.component.input.color.border.focus}`,
|
border: `1px solid ${theme.component.input.color.border.default}`,
|
||||||
},
|
":hover": {
|
||||||
}),
|
border: `1px solid ${theme.component.input.color.border.hover}`,
|
||||||
multiValue: (styles) => ({
|
},
|
||||||
...styles,
|
":focus": {
|
||||||
color: theme.color.text.default,
|
border: `1px solid ${theme.component.input.color.border.focus}`,
|
||||||
backgroundColor: theme.component.ui.color.background.default,
|
},
|
||||||
borderRadius: "10px",
|
}),
|
||||||
}),
|
multiValue: (styles) => ({
|
||||||
multiValueLabel: (styles) => ({
|
...styles,
|
||||||
...styles,
|
color: theme.color.text.default,
|
||||||
color: theme.color.text.default,
|
backgroundColor:
|
||||||
marginLeft: "0.2rem",
|
theme.component.ui.color.background.default,
|
||||||
}),
|
borderRadius: "10px",
|
||||||
multiValueRemove: (styles) => ({
|
}),
|
||||||
...styles,
|
multiValueLabel: (styles) => ({
|
||||||
marginRight: "0.3rem",
|
...styles,
|
||||||
cursor: "pointer",
|
color: theme.color.text.default,
|
||||||
color: theme.component.input.color.placeHolder,
|
marginLeft: "0.2rem",
|
||||||
":hover": {
|
}),
|
||||||
color: theme.color.text.default,
|
multiValueRemove: (styles) => ({
|
||||||
},
|
...styles,
|
||||||
}),
|
marginRight: "0.3rem",
|
||||||
}}
|
cursor: "pointer",
|
||||||
defaultValue={selectedTags}
|
color: theme.component.input.color.placeHolder,
|
||||||
onChange={onChange}
|
":hover": {
|
||||||
options={options}
|
color: theme.color.text.default,
|
||||||
isMulti
|
},
|
||||||
/>
|
}),
|
||||||
</StyledReactTagsContainer>
|
}}
|
||||||
)
|
defaultValue={selectedTags}
|
||||||
|
onChange={onChange}
|
||||||
|
options={options}
|
||||||
|
isMulti
|
||||||
|
/>
|
||||||
|
</StyledReactTagsContainer>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagSelect
|
export default TagSelect
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
color: ${(props) => props.theme.theme.component.anchor.color.default};
|
color: ${(props) => props.theme.theme.component.anchor.color.default};
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${(props) => props.theme.theme.component.anchor.color.hover};
|
color: ${(props) => props.theme.theme.component.anchor.color.hover};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: ${(props) => props.theme.theme.component.anchor.color.active};
|
color: ${(props) =>
|
||||||
}
|
props.theme.theme.component.anchor.color.active};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* The "#" thingy used beside headers */
|
/* The "#" thingy used beside headers */
|
||||||
a.header-anchor {
|
a.header-anchor {
|
||||||
/* compensate for navbar height*/
|
/* compensate for navbar height*/
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
color: ${(props) => props.theme.theme.component.anchor.color.header};
|
color: ${(props) => props.theme.theme.component.anchor.color.header};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* footnote anchors */
|
/* footnote anchors */
|
||||||
a[id^="fnref"] {
|
a[id^="fnref"] {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
blockquote {
|
blockquote {
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.blockQuote.color.background};
|
theme.theme.component.blockQuote.color.background};
|
||||||
border-left: 0.4rem solid
|
border-left: 0.4rem solid
|
||||||
${({ theme }) => theme.theme.component.blockQuote.color.borderLeft};
|
${({ theme }) => theme.theme.component.blockQuote.color.borderLeft};
|
||||||
padding-top: 0.1rem;
|
padding-top: 0.1rem;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
padding-bottom: 0.1rem;
|
padding-bottom: 0.1rem;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
|
|
||||||
@media screen and (max-width: ${({ theme }) =>
|
@media screen and (max-width: ${({ theme }) =>
|
||||||
theme.theme.maxDisplayWidth.mobile}) {
|
theme.theme.maxDisplayWidth.mobile}) {
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
/* style */
|
/* style */
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
/* size */
|
/* size */
|
||||||
|
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
min-width: 2.5rem;
|
min-width: 2.5rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 1rem 0 1rem;
|
padding: 0 1rem 0 1rem;
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
/* color */
|
/* color */
|
||||||
|
|
||||||
color: ${({ theme }) => theme.theme.color.text.default};
|
color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.ui.color.background.default};
|
theme.theme.component.ui.color.background.default};
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.ui.color.background.hover};
|
theme.theme.component.ui.color.background.hover};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* animation */
|
/* animation */
|
||||||
|
|
||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
/* default width and height */
|
/* default width and height */
|
||||||
width: 13px;
|
width: 13px;
|
||||||
height: 13px;
|
height: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"][disabled][checked] {
|
input[type="checkbox"][disabled][checked] {
|
||||||
filter: invert(100%) brightness(5);
|
filter: invert(100%) brightness(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"][disabled] {
|
input[type="checkbox"][disabled] {
|
||||||
filter: invert(100%) brightness(5);
|
filter: invert(100%) brightness(5);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
/* highlight.js code style */
|
/* highlight.js code style */
|
||||||
${({ theme }) => theme.theme.component.code.block.style}
|
${({ theme }) => theme.theme.component.code.block.style}
|
||||||
|
|
||||||
/* inline code */
|
/* inline code */
|
||||||
:not(pre) > code {
|
:not(pre) > code {
|
||||||
font-family: ${({ theme }) => theme.theme.font.monospace};
|
font-family: ${({ theme }) => theme.theme.font.monospace};
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
color: ${({ theme }) => theme.theme.component.code.inline.color.text};
|
color: ${({ theme }) => theme.theme.component.code.inline.color.text};
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.code.inline.color.background};
|
theme.theme.component.code.inline.color.background};
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
${({ theme }) => theme.theme.component.code.inline.color.border};
|
${({ theme }) => theme.theme.component.code.inline.color.border};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* code block */
|
/* code block */
|
||||||
pre > code {
|
pre > code {
|
||||||
font-family: ${(props) => props.theme.theme.font.monospace};
|
font-family: ${(props) => props.theme.theme.font.monospace};
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
${({ theme }) => theme.theme.component.code.block.color.border};
|
${({ theme }) => theme.theme.component.code.block.color.border};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* // todo: fix highlight not working properly when scrolled horizontally // */
|
/* // todo: fix highlight not working properly when scrolled horizontally // */
|
||||||
.highlighted-line {
|
.highlighted-line {
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.code.block.color.highlight};
|
theme.theme.component.code.block.color.highlight};
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
min-width: min-content;
|
min-width: min-content;
|
||||||
margin: 0 -1rem;
|
margin: 0 -1rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -14,44 +14,44 @@ import markCSS from "./mark"
|
||||||
import katexCSS from "./katex"
|
import katexCSS from "./katex"
|
||||||
|
|
||||||
const globalCSS = css`
|
const globalCSS = css`
|
||||||
body {
|
body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
/* size */
|
/* size */
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
/* style */
|
/* style */
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
|
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-family: ${({ theme }) => theme.theme.font.sansSerif};
|
font-family: ${({ theme }) => theme.theme.font.sansSerif};
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
/* color */
|
/* color */
|
||||||
|
|
||||||
background-color: ${({ theme }) => theme.theme.color.background};
|
background-color: ${({ theme }) => theme.theme.color.background};
|
||||||
color: ${({ theme }) => theme.theme.color.text.default};
|
color: ${({ theme }) => theme.theme.color.text.default};
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
transition: color 0.1s linear;
|
transition: color 0.1s linear;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
scroll-margin: 4rem;
|
scroll-margin: 4rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,38 +1,38 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
/* intentionally left out h1 */
|
/* intentionally left out h1 */
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-indent: 0.5rem;
|
text-indent: 0.5rem;
|
||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-indent: 1rem;
|
text-indent: 1rem;
|
||||||
}
|
}
|
||||||
h5 {
|
h5 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-indent: 1.5rem;
|
text-indent: 1.5rem;
|
||||||
}
|
}
|
||||||
h6 {
|
h6 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-indent: 2rem;
|
text-indent: 2rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
hr {
|
hr {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid;
|
border-bottom: 1px solid;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
// prevent overflowing on small displays
|
// prevent overflowing on small displays
|
||||||
.katex-html {
|
.katex-html {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
/* https://www.rgagnon.com/jsdetails/js-nice-effect-the-KBD-tag.html */
|
/* https://www.rgagnon.com/jsdetails/js-nice-effect-the-KBD-tag.html */
|
||||||
kbd {
|
kbd {
|
||||||
margin: 0px 0.1em;
|
margin: 0px 0.1em;
|
||||||
padding: 0.1em 0.6em;
|
padding: 0.1em 0.6em;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid ${({ theme }) => theme.theme.component.kbd.color.border};
|
border: 1px solid
|
||||||
color: ${({ theme }) => theme.theme.component.kbd.color.text};
|
${({ theme }) => theme.theme.component.kbd.color.border};
|
||||||
line-height: 1.4;
|
color: ${({ theme }) => theme.theme.component.kbd.color.text};
|
||||||
font-size: 13.5px;
|
line-height: 1.4;
|
||||||
display: inline-block;
|
font-size: 13.5px;
|
||||||
box-shadow: 0px 1px 0px
|
display: inline-block;
|
||||||
${({ theme }) => theme.theme.component.kbd.color.outerShadow},
|
box-shadow: 0px 1px 0px
|
||||||
inset 0px 0px 0px 2px
|
${({ theme }) => theme.theme.component.kbd.color.outerShadow},
|
||||||
${({ theme }) => theme.theme.component.kbd.color.innerShadow};
|
inset 0px 0px 0px 2px
|
||||||
background-color: ${({ theme }) =>
|
${({ theme }) => theme.theme.component.kbd.color.innerShadow};
|
||||||
theme.theme.component.kbd.color.background};
|
background-color: ${({ theme }) =>
|
||||||
}
|
theme.theme.component.kbd.color.background};
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
mark {
|
mark {
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.mark.color.background};
|
theme.theme.component.mark.color.background};
|
||||||
color: ${({ theme }) => theme.theme.component.mark.color.text};
|
color: ${({ theme }) => theme.theme.component.mark.color.text};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
body::-webkit-scrollbar {
|
body::-webkit-scrollbar {
|
||||||
width: ${(props) => props.theme.theme.component.scrollbar.width};
|
width: ${(props) => props.theme.theme.component.scrollbar.width};
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar-track {
|
body::-webkit-scrollbar-track {
|
||||||
border-radius: ${(props) =>
|
border-radius: ${(props) =>
|
||||||
props.theme.theme.component.scrollbar.borderRadius};
|
props.theme.theme.component.scrollbar.borderRadius};
|
||||||
background: ${(props) => props.theme.theme.component.scrollbar.color.track};
|
background: ${(props) =>
|
||||||
box-shadow: inset 0 0 5px rgb(0 0 0 / 10%);
|
props.theme.theme.component.scrollbar.color.track};
|
||||||
}
|
box-shadow: inset 0 0 5px rgb(0 0 0 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar-thumb {
|
body::-webkit-scrollbar-thumb {
|
||||||
border-radius: ${(props) =>
|
border-radius: ${(props) =>
|
||||||
props.theme.theme.component.scrollbar.borderRadius};
|
props.theme.theme.component.scrollbar.borderRadius};
|
||||||
background: ${(props) => props.theme.theme.component.scrollbar.color.thumb};
|
background: ${(props) =>
|
||||||
box-shadow: inset 0 0 10px rgb(0 0 0 / 20%);
|
props.theme.theme.component.scrollbar.color.thumb};
|
||||||
}
|
box-shadow: inset 0 0 10px rgb(0 0 0 / 20%);
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import { css } from "styled-components"
|
import { css } from "styled-components"
|
||||||
|
|
||||||
export default css`
|
export default css`
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
${({ theme }) => theme.theme.component.table.color.border};
|
${({ theme }) => theme.theme.component.table.color.border};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* table alternating color */
|
/* table alternating color */
|
||||||
tr:nth-child(even) {
|
tr:nth-child(even) {
|
||||||
background-color: ${({ theme }) =>
|
background-color: ${({ theme }) =>
|
||||||
theme.theme.component.table.color.even};
|
theme.theme.component.table.color.even};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -3,53 +3,54 @@ import { ChangeEventHandler } from "react"
|
||||||
|
|
||||||
/* NEW (START) */
|
/* NEW (START) */
|
||||||
const setDark = () => {
|
const setDark = () => {
|
||||||
localStorage.setItem("theme", "dark")
|
localStorage.setItem("theme", "dark")
|
||||||
document.documentElement.setAttribute("data-theme", "dark")
|
document.documentElement.setAttribute("data-theme", "dark")
|
||||||
}
|
}
|
||||||
|
|
||||||
const setLight = () => {
|
const setLight = () => {
|
||||||
localStorage.setItem("theme", "light")
|
localStorage.setItem("theme", "light")
|
||||||
document.documentElement.setAttribute("data-theme", "light")
|
document.documentElement.setAttribute("data-theme", "light")
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedTheme = localStorage.getItem("theme")
|
const storedTheme = localStorage.getItem("theme")
|
||||||
|
|
||||||
const prefersDark =
|
const prefersDark =
|
||||||
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
window.matchMedia &&
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
|
||||||
const defaultDark =
|
const defaultDark =
|
||||||
storedTheme === "dark" || (storedTheme === null && prefersDark)
|
storedTheme === "dark" || (storedTheme === null && prefersDark)
|
||||||
|
|
||||||
if (defaultDark) {
|
if (defaultDark) {
|
||||||
setDark()
|
setDark()
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
|
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setDark()
|
setDark()
|
||||||
} else {
|
} else {
|
||||||
setLight()
|
setLight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* NEW (END) */
|
/* NEW (END) */
|
||||||
|
|
||||||
const DarkMode = () => {
|
const DarkMode = () => {
|
||||||
return (
|
return (
|
||||||
<div className="toggle-theme-wrapper">
|
<div className="toggle-theme-wrapper">
|
||||||
<span>☀️</span>
|
<span>☀️</span>
|
||||||
<label className="toggle-theme" htmlFor="checkbox">
|
<label className="toggle-theme" htmlFor="checkbox">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="checkbox"
|
id="checkbox"
|
||||||
// NEW
|
// NEW
|
||||||
onChange={toggleTheme}
|
onChange={toggleTheme}
|
||||||
defaultChecked={defaultDark}
|
defaultChecked={defaultDark}
|
||||||
/>
|
/>
|
||||||
<div className="slider round"></div>
|
<div className="slider round"></div>
|
||||||
</label>
|
</label>
|
||||||
<span>🌒</span>
|
<span>🌒</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DarkMode
|
export default DarkMode
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "@styled/typescript-styled-plugin",
|
"name": "@styled/typescript-styled-plugin",
|
||||||
"validate": false
|
"validate": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "types/**/*"]
|
"include": ["src/**/*", "types/**/*"]
|
||||||
}
|
}
|
||||||
|
|
6
apps/blog/types/react-date-range.d.ts
vendored
6
apps/blog/types/react-date-range.d.ts
vendored
|
@ -1,7 +1,7 @@
|
||||||
import "react-date-range"
|
import "react-date-range"
|
||||||
|
|
||||||
declare module "react-date-range" {
|
declare module "react-date-range" {
|
||||||
export interface DateRangeProps extends Range, CommonCalendarProps {
|
export interface DateRangeProps extends Range, CommonCalendarProps {
|
||||||
retainEndDateOnFirstSelection?: boolean | undefined
|
retainEndDateOnFirstSelection?: boolean | undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
apps/blog/types/read-time-estimate.d.ts
vendored
34
apps/blog/types/read-time-estimate.d.ts
vendored
|
@ -1,19 +1,19 @@
|
||||||
declare module "read-time-estimate" {
|
declare module "read-time-estimate" {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export default function toc(
|
export default function toc(
|
||||||
string: string,
|
string: string,
|
||||||
customWordTime: number,
|
customWordTime: number,
|
||||||
customImageTime: number,
|
customImageTime: number,
|
||||||
chineseKoreanReadTime: number,
|
chineseKoreanReadTime: number,
|
||||||
imageTags: string[]
|
imageTags: string[]
|
||||||
): {
|
): {
|
||||||
humanizedDuration: string
|
humanizedDuration: string
|
||||||
duration: number
|
duration: number
|
||||||
totalWords: number
|
totalWords: number
|
||||||
wordTime: number
|
wordTime: number
|
||||||
totalImages: number
|
totalImages: number
|
||||||
imageTime: number
|
imageTime: number
|
||||||
otherLanguageTimeCharacters: number
|
otherLanguageTimeCharacters: number
|
||||||
otherLanguageTime: number
|
otherLanguageTime: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import type { Theme } from "@developomp-site/theme"
|
||||||
import { SiteTheme } from "../src/globalContext"
|
import { SiteTheme } from "../src/globalContext"
|
||||||
|
|
||||||
declare module "styled-components" {
|
declare module "styled-components" {
|
||||||
export interface DefaultTheme {
|
export interface DefaultTheme {
|
||||||
currentTheme: SiteTheme
|
currentTheme: SiteTheme
|
||||||
theme: Theme
|
theme: Theme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
{
|
{
|
||||||
"hosting": [
|
"hosting": [
|
||||||
{
|
{
|
||||||
"target": "main",
|
"target": "main",
|
||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"public": "apps/main/build",
|
"public": "apps/main/build",
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "**",
|
"source": "**",
|
||||||
"destination": "/index.html"
|
"destination": "/index.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ignore": ["**/.*"]
|
"ignore": ["**/.*"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"target": "blog",
|
"target": "blog",
|
||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"public": "apps/blog/build",
|
"public": "apps/blog/build",
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "**",
|
"source": "**",
|
||||||
"destination": "/index.html"
|
"destination": "/index.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ignore": ["**/.*"]
|
"ignore": ["**/.*"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"target": "portfolio",
|
"target": "portfolio",
|
||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"public": "apps/portfolio/dist",
|
"public": "apps/portfolio/dist",
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "**",
|
"source": "**",
|
||||||
"destination": "/index.html"
|
"destination": "/index.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ignore": ["**/.*"]
|
"ignore": ["**/.*"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
32
package.json
32
package.json
|
@ -1,18 +1,18 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "^pnpm@7.0.0",
|
"packageManager": "^pnpm@7.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"dev": "turbo run dev --no-cache --parallel --continue",
|
"dev": "turbo run dev --no-cache --parallel --continue",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"clean": "turbo run clean && rm -rf node_modules",
|
"clean": "turbo run clean && rm -rf node_modules",
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@developomp-site/eslint-config": "workspace:*",
|
"@developomp-site/eslint-config": "workspace:*",
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.29.0",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.0",
|
"prettier-plugin-tailwindcss": "^0.2.0",
|
||||||
"turbo": "^1.10.6"
|
"turbo": "^1.10.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/blog-content",
|
"name": "@developomp-site/blog-content",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**"
|
"dist/**"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ts-node --experimental-specifier-resolution=node ./src",
|
"build": "ts-node --experimental-specifier-resolution=node ./src",
|
||||||
"clean": "rm -rf .turbo node_modules dist"
|
"clean": "rm -rf .turbo node_modules dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@developomp-site/tsconfig": "workspace:*",
|
"@developomp-site/tsconfig": "workspace:*",
|
||||||
"@types/ejs": "^3.1.1",
|
"@types/ejs": "^3.1.1",
|
||||||
"@types/katex": "^0.14.0",
|
"@types/katex": "^0.14.0",
|
||||||
"@types/markdown-it": "^12.2.3",
|
"@types/markdown-it": "^12.2.3",
|
||||||
"@types/read-time-estimate": "^0.0.0",
|
"@types/read-time-estimate": "^0.0.0",
|
||||||
"@types/svgo": "^3.0.0",
|
"@types/svgo": "^3.0.0",
|
||||||
"@types/tinycolor2": "^1.4.3",
|
"@types/tinycolor2": "^1.4.3",
|
||||||
"canvas": "^2.11.2",
|
"canvas": "^2.11.2",
|
||||||
"ejs": "^3.1.8",
|
"ejs": "^3.1.8",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"markdown-it-anchor": "^8.6.5",
|
"markdown-it-anchor": "^8.6.5",
|
||||||
"markdown-it-attrs": "^4.1.4",
|
"markdown-it-attrs": "^4.1.4",
|
||||||
"markdown-it-footnote": "^3.0.3",
|
"markdown-it-footnote": "^3.0.3",
|
||||||
"markdown-it-highlight-lines": "^1.0.2",
|
"markdown-it-highlight-lines": "^1.0.2",
|
||||||
"markdown-it-mark": "^3.0.1",
|
"markdown-it-mark": "^3.0.1",
|
||||||
"markdown-it-sub": "^1.0.0",
|
"markdown-it-sub": "^1.0.0",
|
||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"markdown-it-task-checkbox": "^1.0.6",
|
"markdown-it-task-checkbox": "^1.0.6",
|
||||||
"markdown-it-texmath": "^1.0.0",
|
"markdown-it-texmath": "^1.0.0",
|
||||||
"markdown-toc": "^1.2.0",
|
"markdown-toc": "^1.2.0",
|
||||||
"read-time-estimate": "^0.0.3",
|
"read-time-estimate": "^0.0.3",
|
||||||
"simple-icons": "^7.21.0",
|
"simple-icons": "^7.21.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"svgo": "^3.0.2",
|
"svgo": "^3.0.2",
|
||||||
"tinycolor2": "^1.4.2",
|
"tinycolor2": "^1.4.2",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,19 +16,19 @@ import postProcess from "./postProcess"
|
||||||
import { ContentMap, ParseMode, PortfolioData, SeriesMap } from "./types/types"
|
import { ContentMap, ParseMode, PortfolioData, SeriesMap } from "./types/types"
|
||||||
|
|
||||||
export const contentMap: ContentMap = {
|
export const contentMap: ContentMap = {
|
||||||
date: {},
|
date: {},
|
||||||
tags: {},
|
tags: {},
|
||||||
meta: {
|
meta: {
|
||||||
tags: [],
|
tags: [],
|
||||||
},
|
},
|
||||||
posts: {},
|
posts: {},
|
||||||
series: {},
|
series: {},
|
||||||
unsearchable: {},
|
unsearchable: {},
|
||||||
}
|
}
|
||||||
export const seriesMap: SeriesMap = {}
|
export const seriesMap: SeriesMap = {}
|
||||||
export const portfolioData: PortfolioData = {
|
export const portfolioData: PortfolioData = {
|
||||||
skills: new Set(),
|
skills: new Set(),
|
||||||
projects: {},
|
projects: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,8 +36,8 @@ export const portfolioData: PortfolioData = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.rmSync("dist", { recursive: true })
|
fs.rmSync("dist", { recursive: true })
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,16 +45,16 @@ try {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!fs.lstatSync(markdownPath).isDirectory())
|
if (!fs.lstatSync(markdownPath).isDirectory())
|
||||||
throw Error("Invalid markdown path")
|
throw Error("Invalid markdown path")
|
||||||
|
|
||||||
if (!fs.lstatSync(markdownPath + "/posts").isDirectory())
|
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())
|
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())
|
if (!fs.lstatSync(markdownPath + "/series").isDirectory())
|
||||||
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
|
throw Error(`Cannot find directory: ${markdownPath + "/posts"}`)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse
|
* Parse
|
||||||
|
@ -77,11 +77,11 @@ postProcess()
|
||||||
|
|
||||||
fs.writeFileSync(mapFilePath, JSON.stringify(contentMap))
|
fs.writeFileSync(mapFilePath, JSON.stringify(contentMap))
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
portfolioFilePath,
|
portfolioFilePath,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
...portfolioData,
|
...portfolioData,
|
||||||
skills: Array.from(portfolioData.skills),
|
skills: Array.from(portfolioData.skills),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
saveIndex()
|
saveIndex()
|
||||||
|
|
|
@ -24,37 +24,37 @@ import { MarkdownData, ParseMode } from "./types/types"
|
||||||
const slugifyIt = (s: string) => slugify(s, { lower: true, strict: true })
|
const slugifyIt = (s: string) => slugify(s, { lower: true, strict: true })
|
||||||
|
|
||||||
const md = markdownIt({
|
const md = markdownIt({
|
||||||
// https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
|
// https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
|
||||||
highlight: (str, lang) => {
|
highlight: (str, lang) => {
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return hljs.highlight(str, { language: lang }).value
|
return hljs.highlight(str, { language: lang }).value
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "" // use external default escaping
|
return "" // use external default escaping
|
||||||
},
|
},
|
||||||
html: true,
|
html: true,
|
||||||
})
|
})
|
||||||
.use(markdownItTexMath, {
|
.use(markdownItTexMath, {
|
||||||
engine: katex,
|
engine: katex,
|
||||||
delimiters: "dollars",
|
delimiters: "dollars",
|
||||||
})
|
})
|
||||||
.use(markdownItAnchor, {
|
.use(markdownItAnchor, {
|
||||||
permalink: markdownItAnchor.permalink.ariaHidden({
|
permalink: markdownItAnchor.permalink.ariaHidden({
|
||||||
placement: "before",
|
placement: "before",
|
||||||
symbol: "#",
|
symbol: "#",
|
||||||
renderHref: (s) => `#${slugifyIt(s)}`,
|
renderHref: (s) => `#${slugifyIt(s)}`,
|
||||||
}),
|
}),
|
||||||
slugify: slugifyIt,
|
slugify: slugifyIt,
|
||||||
})
|
})
|
||||||
.use(markdownItTaskCheckbox)
|
.use(markdownItTaskCheckbox)
|
||||||
.use(markDownItMark)
|
.use(markDownItMark)
|
||||||
.use(markdownItSub)
|
.use(markdownItSub)
|
||||||
.use(markdownItSup)
|
.use(markdownItSup)
|
||||||
.use(highlightLines)
|
.use(highlightLines)
|
||||||
.use(markdownItFootnote)
|
.use(markdownItFootnote)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parse the front matter if it exists
|
* parse the front matter if it exists
|
||||||
|
@ -64,70 +64,70 @@ const md = markdownIt({
|
||||||
* @param {ParseMode} mode
|
* @param {ParseMode} mode
|
||||||
*/
|
*/
|
||||||
export default function parseMarkdown(
|
export default function parseMarkdown(
|
||||||
markdownRaw: string,
|
markdownRaw: string,
|
||||||
path: string,
|
path: string,
|
||||||
mode: ParseMode
|
mode: ParseMode
|
||||||
): MarkdownData {
|
): MarkdownData {
|
||||||
const fileHasFrontMatter = markdownRaw.startsWith("---")
|
const fileHasFrontMatter = markdownRaw.startsWith("---")
|
||||||
|
|
||||||
const frontMatter = fileHasFrontMatter
|
const frontMatter = fileHasFrontMatter
|
||||||
? matter(markdownRaw.slice(0, nthIndex(markdownRaw, "---", 2) + 3)).data
|
? matter(markdownRaw.slice(0, nthIndex(markdownRaw, "---", 2) + 3)).data
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
if (fileHasFrontMatter) {
|
if (fileHasFrontMatter) {
|
||||||
if (mode != ParseMode.PORTFOLIO) {
|
if (mode != ParseMode.PORTFOLIO) {
|
||||||
if (!frontMatter.title)
|
if (!frontMatter.title)
|
||||||
throw Error(`Title is not defined in file: ${path}`)
|
throw Error(`Title is not defined in file: ${path}`)
|
||||||
|
|
||||||
if (mode != ParseMode.UNSEARCHABLE && !frontMatter.date)
|
if (mode != ParseMode.UNSEARCHABLE && !frontMatter.date)
|
||||||
throw Error(`Date is not defined in file: ${path}`)
|
throw Error(`Date is not defined in file: ${path}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === ParseMode.PORTFOLIO) {
|
if (mode === ParseMode.PORTFOLIO) {
|
||||||
if (frontMatter.overview) {
|
if (frontMatter.overview) {
|
||||||
frontMatter.overview = md.render(frontMatter.overview)
|
frontMatter.overview = md.render(frontMatter.overview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// work with rendered DOM
|
// work with rendered DOM
|
||||||
//
|
//
|
||||||
|
|
||||||
const dom = new JSDOM(
|
const dom = new JSDOM(
|
||||||
md.render(
|
md.render(
|
||||||
fileHasFrontMatter
|
fileHasFrontMatter
|
||||||
? markdownRaw.slice(nthIndex(markdownRaw, "---", 2) + 3)
|
? markdownRaw.slice(nthIndex(markdownRaw, "---", 2) + 3)
|
||||||
: markdownRaw
|
: markdownRaw
|
||||||
) || ""
|
) || ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// add .hljs class to all block codes
|
// add .hljs class to all block codes
|
||||||
|
|
||||||
dom.window.document.querySelectorAll("pre > code").forEach((item) => {
|
dom.window.document.querySelectorAll("pre > code").forEach((item) => {
|
||||||
item.classList.add("hljs")
|
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) => {
|
dom.window.document.querySelectorAll("table").forEach((item) => {
|
||||||
// `element` is the element you want to wrap
|
// `element` is the element you want to wrap
|
||||||
const parent = item.parentNode
|
const parent = item.parentNode
|
||||||
if (!parent) return // stop if table doesn't have a parent node
|
if (!parent) return // stop if table doesn't have a parent node
|
||||||
const wrapper = dom.window.document.createElement("div")
|
const wrapper = dom.window.document.createElement("div")
|
||||||
wrapper.style.overflowX = "auto"
|
wrapper.style.overflowX = "auto"
|
||||||
|
|
||||||
parent.replaceChild(wrapper, item)
|
parent.replaceChild(wrapper, item)
|
||||||
wrapper.appendChild(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 {
|
export function generateToc(markdownRaw: string): string {
|
||||||
return md.render(toc(markdownRaw).content, {
|
return md.render(toc(markdownRaw).content, {
|
||||||
slugify: slugifyIt,
|
slugify: slugifyIt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
{
|
{
|
||||||
"Programming Languages": [
|
"Programming Languages": [
|
||||||
"javascript",
|
"javascript",
|
||||||
"typescript",
|
"typescript",
|
||||||
"python",
|
"python",
|
||||||
"rust",
|
"rust",
|
||||||
"csharp C#"
|
"csharp C#"
|
||||||
],
|
],
|
||||||
"Web Front End": ["react", "svelte", "tailwindcss Tailwind"],
|
"Web Front End": ["react", "svelte", "tailwindcss Tailwind"],
|
||||||
"Desktop Front End": ["gtk", "electron", "tauri"],
|
"Desktop Front End": ["gtk", "electron", "tauri"],
|
||||||
"Back End": ["firebase"],
|
"Back End": ["firebase"],
|
||||||
"DevOps": ["docker", "githubactions GH Actions"],
|
"DevOps": ["docker", "githubactions GH Actions"],
|
||||||
"Game Development": ["unity"],
|
"Game Development": ["unity"],
|
||||||
"Etc": [
|
"Etc": [
|
||||||
"figma",
|
"figma",
|
||||||
"markdown",
|
"markdown",
|
||||||
"notion",
|
"notion",
|
||||||
"google Google-Fu",
|
"google Google-Fu",
|
||||||
"discord Discord Bot"
|
"discord Discord Bot"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
svg {
|
svg {
|
||||||
/* from github */
|
/* from github */
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
|
||||||
sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #777777;
|
color: #777777;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
@ -12,50 +12,50 @@ h3,
|
||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.items-wrapper {
|
.items-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
row-gap: 15px;
|
row-gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-box {
|
.badge-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
|
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-container > svg {
|
.icon-container > svg {
|
||||||
height: 40px !important;
|
height: 40px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.white {
|
.white {
|
||||||
color: white;
|
color: white;
|
||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.black {
|
.black {
|
||||||
color: black;
|
color: black;
|
||||||
fill: black;
|
fill: black;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,127 +12,127 @@ import skills from "./portfolio/skills.json"
|
||||||
import { writeToFile } from "./util"
|
import { writeToFile } from "./util"
|
||||||
|
|
||||||
export default function postProcess() {
|
export default function postProcess() {
|
||||||
sortDates()
|
sortDates()
|
||||||
fillTags()
|
fillTags()
|
||||||
parseSeries()
|
parseSeries()
|
||||||
generatePortfolioSVGs()
|
generatePortfolioSVGs()
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortDates() {
|
function sortDates() {
|
||||||
const TmpDate = contentMap.date
|
const TmpDate = contentMap.date
|
||||||
contentMap.date = {}
|
contentMap.date = {}
|
||||||
Object.keys(TmpDate)
|
Object.keys(TmpDate)
|
||||||
.sort()
|
.sort()
|
||||||
.forEach((sortedDateKey) => {
|
.forEach((sortedDateKey) => {
|
||||||
contentMap.date[sortedDateKey] = TmpDate[sortedDateKey]
|
contentMap.date[sortedDateKey] = TmpDate[sortedDateKey]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillTags() {
|
function fillTags() {
|
||||||
contentMap.meta.tags = Object.keys(contentMap.tags)
|
contentMap.meta.tags = Object.keys(contentMap.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSeries() {
|
function parseSeries() {
|
||||||
// sort series map
|
// sort series map
|
||||||
for (const seriesURL in seriesMap) {
|
for (const seriesURL in seriesMap) {
|
||||||
seriesMap[seriesURL].sort((a, b) => {
|
seriesMap[seriesURL].sort((a, b) => {
|
||||||
if (a.index < b.index) return -1
|
if (a.index < b.index) return -1
|
||||||
if (a.index > b.index) return 1
|
if (a.index > b.index) return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// series length and order
|
// series length and order
|
||||||
for (const seriesURL in seriesMap) {
|
for (const seriesURL in seriesMap) {
|
||||||
contentMap.series[seriesURL].length = seriesMap[seriesURL].length
|
contentMap.series[seriesURL].length = seriesMap[seriesURL].length
|
||||||
contentMap.series[seriesURL].order = seriesMap[seriesURL].map(
|
contentMap.series[seriesURL].order = seriesMap[seriesURL].map(
|
||||||
(item) => item.url
|
(item) => item.url
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatePortfolioSVGs() {
|
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: {
|
const data: {
|
||||||
[key: string]: Badge[] | { [key: string]: Badge[] }
|
[key: string]: Badge[] | { [key: string]: Badge[] }
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
// 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
|
// 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
|
||||||
|
|
||||||
for (const key in skills) {
|
for (const key in skills) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (skills[key] instanceof Array) {
|
if (skills[key] instanceof Array) {
|
||||||
if (!data[key]) {
|
if (!data[key]) {
|
||||||
data[key] = []
|
data[key] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
;(skills[key] as string[]).forEach((badge) =>
|
;(skills[key] as string[]).forEach((badge) =>
|
||||||
(data[key] as Badge[]).push(parseBadge(badge))
|
(data[key] as Badge[]).push(parseBadge(badge))
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (const subKey in skills[key]) {
|
for (const subKey in skills[key]) {
|
||||||
if (!data[key]) data[key] = {}
|
if (!data[key]) data[key] = {}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (!data[key][subKey]) {
|
if (!data[key][subKey]) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
data[key][subKey] = []
|
data[key][subKey] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
skills[key][subKey].forEach((badge: string) =>
|
skills[key][subKey].forEach((badge: string) =>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(data[key][subKey] as Badge[]).push(parseBadge(badge))
|
(data[key][subKey] as Badge[]).push(parseBadge(badge))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderedSVG = ejs.render(
|
const renderedSVG = ejs.render(
|
||||||
readFileSync("./src/portfolio/skills.ejs", "utf-8"),
|
readFileSync("./src/portfolio/skills.ejs", "utf-8"),
|
||||||
{ style, data },
|
{ style, data },
|
||||||
{ views: ["./src/portfolio"] }
|
{ views: ["./src/portfolio"] }
|
||||||
)
|
)
|
||||||
|
|
||||||
writeToFile(
|
writeToFile(
|
||||||
"./dist/public/img/skills.svg",
|
"./dist/public/img/skills.svg",
|
||||||
optimize(renderedSVG, { multipass: true }).data
|
optimize(renderedSVG, { multipass: true }).data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBadge(badgeRaw: string): Badge {
|
function parseBadge(badgeRaw: string): Badge {
|
||||||
const isMultiWord = badgeRaw.includes(" ")
|
const isMultiWord = badgeRaw.includes(" ")
|
||||||
const words = badgeRaw.split(" ")
|
const words = badgeRaw.split(" ")
|
||||||
const slug = words[0]
|
const slug = words[0]
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const icon = icons["si" + slug[0].toUpperCase() + slug.slice(1)]
|
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 {
|
return {
|
||||||
svg: icon.svg,
|
svg: icon.svg,
|
||||||
hex: color.toHexString(),
|
hex: color.toHexString(),
|
||||||
isDark: color.isDark(),
|
isDark: color.isDark(),
|
||||||
title: isMultiWord ? words.slice(1).join(" ") : icon.title,
|
title: isMultiWord ? words.slice(1).join(" ") : icon.title,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,15 @@ import { ParseMode } from "../types/types"
|
||||||
* Data that's passed from {@link parseFile} to other function
|
* Data that's passed from {@link parseFile} to other function
|
||||||
*/
|
*/
|
||||||
export interface DataToPass {
|
export interface DataToPass {
|
||||||
path: string
|
path: string
|
||||||
urlPath: string
|
urlPath: string
|
||||||
markdownRaw: string
|
markdownRaw: string
|
||||||
markdownData: {
|
markdownData: {
|
||||||
content: string
|
content: string
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
humanizedDuration: string
|
humanizedDuration: string
|
||||||
totalWords: number
|
totalWords: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,23 +33,23 @@ export interface DataToPass {
|
||||||
* @param {string} path - path of file or folder
|
* @param {string} path - path of file or folder
|
||||||
*/
|
*/
|
||||||
export function recursiveParse(mode: ParseMode, path: string): void {
|
export function recursiveParse(mode: ParseMode, path: string): void {
|
||||||
// get name of the file or folder that's currently being parsed
|
// get name of the file or folder that's currently being parsed
|
||||||
const fileOrFolderName = path2FileOrFolderName(path)
|
const fileOrFolderName = path2FileOrFolderName(path)
|
||||||
|
|
||||||
// stop if the file or folder starts with a underscore
|
// stop if the file or folder starts with a underscore
|
||||||
if (fileOrFolderName.startsWith("_")) return
|
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 directory, call this function to every files/directories in it
|
||||||
// if it's a file, parse it and then save it to file
|
// if it's a file, parse it and then save it to file
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
fs.readdirSync(path).map((childPath) => {
|
fs.readdirSync(path).map((childPath) => {
|
||||||
recursiveParse(mode, `${path}/${childPath}`)
|
recursiveParse(mode, `${path}/${childPath}`)
|
||||||
})
|
})
|
||||||
} else if (stats.isFile()) {
|
} else if (stats.isFile()) {
|
||||||
parseFile(mode, path)
|
parseFile(mode, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,50 +59,50 @@ export function recursiveParse(mode: ParseMode, path: string): void {
|
||||||
* @param {string} path - path of the markdown file
|
* @param {string} path - path of the markdown file
|
||||||
*/
|
*/
|
||||||
function parseFile(mode: ParseMode, path: string): void {
|
function parseFile(mode: ParseMode, path: string): void {
|
||||||
// stop if it is not a markdown file
|
// stop if it is not a markdown file
|
||||||
if (!path.endsWith(".md")) {
|
if (!path.endsWith(".md")) {
|
||||||
console.log(`Ignoring non markdown file at: ${path}`)
|
console.log(`Ignoring non markdown file at: ${path}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse markdown
|
* Parse markdown
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const markdownRaw = fs.readFileSync(path, "utf8")
|
const markdownRaw = fs.readFileSync(path, "utf8")
|
||||||
const markdownData = parseMarkdown(markdownRaw, path, mode)
|
const markdownData = parseMarkdown(markdownRaw, path, mode)
|
||||||
const { humanizedDuration, totalWords } = readTimeEstimate(
|
const { humanizedDuration, totalWords } = readTimeEstimate(
|
||||||
markdownData.content,
|
markdownData.content,
|
||||||
275,
|
275,
|
||||||
12,
|
12,
|
||||||
500,
|
500,
|
||||||
["img", "Image"]
|
["img", "Image"]
|
||||||
)
|
)
|
||||||
|
|
||||||
const dataToPass: DataToPass = {
|
const dataToPass: DataToPass = {
|
||||||
path,
|
path,
|
||||||
urlPath: path2URL(path),
|
urlPath: path2URL(path),
|
||||||
markdownRaw,
|
markdownRaw,
|
||||||
markdownData,
|
markdownData,
|
||||||
humanizedDuration,
|
humanizedDuration,
|
||||||
totalWords,
|
totalWords,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case ParseMode.POSTS:
|
case ParseMode.POSTS:
|
||||||
parsePost(dataToPass)
|
parsePost(dataToPass)
|
||||||
break
|
break
|
||||||
|
|
||||||
case ParseMode.SERIES:
|
case ParseMode.SERIES:
|
||||||
parseSeries(dataToPass)
|
parseSeries(dataToPass)
|
||||||
break
|
break
|
||||||
|
|
||||||
case ParseMode.UNSEARCHABLE:
|
case ParseMode.UNSEARCHABLE:
|
||||||
parseUnsearchable(dataToPass)
|
parseUnsearchable(dataToPass)
|
||||||
break
|
break
|
||||||
|
|
||||||
case ParseMode.PORTFOLIO:
|
case ParseMode.PORTFOLIO:
|
||||||
parseProjects(dataToPass)
|
parseProjects(dataToPass)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,65 +8,70 @@ import { DataToPass } from "."
|
||||||
import { PostData } from "../types/types"
|
import { PostData } from "../types/types"
|
||||||
|
|
||||||
export default function parsePost(data: DataToPass): void {
|
export default function parsePost(data: DataToPass): void {
|
||||||
const { urlPath, markdownRaw, markdownData, humanizedDuration, totalWords } =
|
const {
|
||||||
data
|
urlPath,
|
||||||
|
markdownRaw,
|
||||||
|
markdownData,
|
||||||
|
humanizedDuration,
|
||||||
|
totalWords,
|
||||||
|
} = data
|
||||||
|
|
||||||
const postData: PostData = {
|
const postData: PostData = {
|
||||||
title: markdownData.title as string,
|
title: markdownData.title as string,
|
||||||
date: "",
|
date: "",
|
||||||
readTime: humanizedDuration,
|
readTime: humanizedDuration,
|
||||||
wordCount: totalWords,
|
wordCount: totalWords,
|
||||||
tags: [],
|
tags: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dates
|
* Dates
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const postDate = new Date(markdownData.date as string)
|
const postDate = new Date(markdownData.date as string)
|
||||||
postData.date = postDate.toLocaleString("default", {
|
postData.date = postDate.toLocaleString("default", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})
|
})
|
||||||
|
|
||||||
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
|
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
|
||||||
if (contentMap.date[YYYY_MM_DD]) {
|
if (contentMap.date[YYYY_MM_DD]) {
|
||||||
contentMap.date[YYYY_MM_DD].push(urlPath)
|
contentMap.date[YYYY_MM_DD].push(urlPath)
|
||||||
} else {
|
} else {
|
||||||
contentMap.date[YYYY_MM_DD] = [urlPath]
|
contentMap.date[YYYY_MM_DD] = [urlPath]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tags
|
* Tags
|
||||||
*/
|
*/
|
||||||
|
|
||||||
postData.tags = markdownData.tags as string[]
|
postData.tags = markdownData.tags as string[]
|
||||||
if (postData.tags) {
|
if (postData.tags) {
|
||||||
postData.tags.forEach((tag) => {
|
postData.tags.forEach((tag) => {
|
||||||
if (contentMap.tags[tag]) {
|
if (contentMap.tags[tag]) {
|
||||||
contentMap.tags[tag].push(urlPath)
|
contentMap.tags[tag].push(urlPath)
|
||||||
} else {
|
} else {
|
||||||
contentMap.tags[tag] = [urlPath]
|
contentMap.tags[tag] = [urlPath]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
contentMap.posts[urlPath] = postData
|
contentMap.posts[urlPath] = postData
|
||||||
addDocument({
|
addDocument({
|
||||||
title: markdownData.title,
|
title: markdownData.title,
|
||||||
body: markdownData.content,
|
body: markdownData.content,
|
||||||
url: urlPath,
|
url: urlPath,
|
||||||
})
|
})
|
||||||
writeToFile(
|
writeToFile(
|
||||||
`${contentDirectoryPath}${urlPath}.json`,
|
`${contentDirectoryPath}${urlPath}.json`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: markdownData.content,
|
content: markdownData.content,
|
||||||
toc: generateToc(markdownRaw),
|
toc: generateToc(markdownRaw),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,46 +9,46 @@ import { portfolioData } from ".."
|
||||||
import { DataToPass } from "."
|
import { DataToPass } from "."
|
||||||
|
|
||||||
export default function parseProjects(data: DataToPass): void {
|
export default function parseProjects(data: DataToPass): void {
|
||||||
const { urlPath, markdownRaw, markdownData } = data
|
const { urlPath, markdownRaw, markdownData } = data
|
||||||
|
|
||||||
if (markdownData.badges) {
|
if (markdownData.badges) {
|
||||||
;(markdownData.badges as string[]).forEach((slug) => {
|
;(markdownData.badges as string[]).forEach((slug) => {
|
||||||
// todo: handle cases when icon is not on simple-icons
|
// todo: handle cases when icon is not on simple-icons
|
||||||
const icon: SimpleIcon =
|
const icon: SimpleIcon =
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icons["si" + slug[0].toUpperCase() + slug.slice(1)]
|
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
|
// save svg icon
|
||||||
writeToFile(
|
writeToFile(
|
||||||
`${iconsDirectoryPath}/${icon.slug}.json`,
|
`${iconsDirectoryPath}/${icon.slug}.json`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
svg: icon.svg,
|
svg: icon.svg,
|
||||||
hex: color.toHexString(),
|
hex: color.toHexString(),
|
||||||
isDark: color.isDark(),
|
isDark: color.isDark(),
|
||||||
title: icon.title,
|
title: icon.title,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove /projects/ prefix
|
// remove /projects/ prefix
|
||||||
portfolioData.projects[urlPath.replace("/projects/", "")] = {
|
portfolioData.projects[urlPath.replace("/projects/", "")] = {
|
||||||
name: markdownData.name as string,
|
name: markdownData.name as string,
|
||||||
image: markdownData.image as string,
|
image: markdownData.image as string,
|
||||||
overview: markdownData.overview as string,
|
overview: markdownData.overview as string,
|
||||||
badges: (markdownData.badges as string[]) || [],
|
badges: (markdownData.badges as string[]) || [],
|
||||||
repo: (markdownData.repo as string) || "",
|
repo: (markdownData.repo as string) || "",
|
||||||
}
|
}
|
||||||
|
|
||||||
writeToFile(
|
writeToFile(
|
||||||
`${contentDirectoryPath}${urlPath}.json`,
|
`${contentDirectoryPath}${urlPath}.json`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: markdownData.content,
|
content: markdownData.content,
|
||||||
toc: generateToc(markdownRaw),
|
toc: generateToc(markdownRaw),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,140 +8,141 @@ import { DataToPass } from "."
|
||||||
import { PostData } from "../types/types"
|
import { PostData } from "../types/types"
|
||||||
|
|
||||||
export default function parseSeries(data: DataToPass): void {
|
export default function parseSeries(data: DataToPass): void {
|
||||||
const {
|
const {
|
||||||
path,
|
path,
|
||||||
urlPath: _urlPath,
|
urlPath: _urlPath,
|
||||||
markdownRaw,
|
markdownRaw,
|
||||||
markdownData,
|
markdownData,
|
||||||
humanizedDuration,
|
humanizedDuration,
|
||||||
totalWords,
|
totalWords,
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
// last part of the url without the slash
|
// last part of the url without the slash
|
||||||
let lastPath = _urlPath.slice(_urlPath.lastIndexOf("/") + 1)
|
let lastPath = _urlPath.slice(_urlPath.lastIndexOf("/") + 1)
|
||||||
if (!lastPath.includes("_") && !lastPath.startsWith("0"))
|
if (!lastPath.includes("_") && !lastPath.startsWith("0"))
|
||||||
throw Error(`Invalid series file name at: "${path}"`)
|
throw Error(`Invalid series file name at: "${path}"`)
|
||||||
|
|
||||||
// if file is a series descriptor or not (not = regular series post)
|
// if file is a series descriptor or not (not = regular series post)
|
||||||
const isFileDescriptor = lastPath.startsWith("0") && !lastPath.includes("_")
|
const isFileDescriptor = lastPath.startsWith("0") && !lastPath.includes("_")
|
||||||
|
|
||||||
// series post url
|
// series post url
|
||||||
if (isFileDescriptor) {
|
if (isFileDescriptor) {
|
||||||
lastPath = ""
|
lastPath = ""
|
||||||
} else {
|
} else {
|
||||||
lastPath = lastPath
|
lastPath = lastPath
|
||||||
.slice(lastPath.indexOf("_") + 1) // get string after the series index
|
.slice(lastPath.indexOf("_") + 1) // get string after the series index
|
||||||
.replace(/\/$/, "") // remove trailing slash
|
.replace(/\/$/, "") // remove trailing slash
|
||||||
}
|
}
|
||||||
|
|
||||||
// get url until right before the lastPath
|
// get url until right before the lastPath
|
||||||
const urlUntilLastPath = _urlPath.slice(0, _urlPath.lastIndexOf("/") + 1)
|
const urlUntilLastPath = _urlPath.slice(0, _urlPath.lastIndexOf("/") + 1)
|
||||||
|
|
||||||
// remove trailing slash if it's a regular series post
|
// remove trailing slash if it's a regular series post
|
||||||
const urlPath =
|
const urlPath =
|
||||||
(isFileDescriptor
|
(isFileDescriptor
|
||||||
? urlUntilLastPath.replace(/\/$/, "")
|
? urlUntilLastPath.replace(/\/$/, "")
|
||||||
: urlUntilLastPath) + lastPath
|
: urlUntilLastPath) + lastPath
|
||||||
|
|
||||||
// todo: separate interface for series descriptor (no word count and read time)
|
// todo: separate interface for series descriptor (no word count and read time)
|
||||||
const postData: PostData = {
|
const postData: PostData = {
|
||||||
title: markdownData.title as string,
|
title: markdownData.title as string,
|
||||||
date: "",
|
date: "",
|
||||||
readTime: humanizedDuration,
|
readTime: humanizedDuration,
|
||||||
wordCount: totalWords,
|
wordCount: totalWords,
|
||||||
tags: [],
|
tags: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date
|
* Date
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const postDate = new Date(markdownData.date as string)
|
const postDate = new Date(markdownData.date as string)
|
||||||
postData.date = postDate.toLocaleString("default", {
|
postData.date = postDate.toLocaleString("default", {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})
|
})
|
||||||
|
|
||||||
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
|
const YYYY_MM_DD = postDate.toISOString().split("T")[0]
|
||||||
if (contentMap.date[YYYY_MM_DD]) {
|
if (contentMap.date[YYYY_MM_DD]) {
|
||||||
contentMap.date[YYYY_MM_DD].push(urlPath)
|
contentMap.date[YYYY_MM_DD].push(urlPath)
|
||||||
} else {
|
} else {
|
||||||
contentMap.date[YYYY_MM_DD] = [urlPath]
|
contentMap.date[YYYY_MM_DD] = [urlPath]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tags
|
* Tags
|
||||||
*/
|
*/
|
||||||
|
|
||||||
postData.tags = markdownData.tags as string[]
|
postData.tags = markdownData.tags as string[]
|
||||||
if (postData.tags) {
|
if (postData.tags) {
|
||||||
postData.tags.forEach((tag) => {
|
postData.tags.forEach((tag) => {
|
||||||
if (contentMap.tags[tag]) {
|
if (contentMap.tags[tag]) {
|
||||||
contentMap.tags[tag].push(urlPath)
|
contentMap.tags[tag].push(urlPath)
|
||||||
} else {
|
} else {
|
||||||
contentMap.tags[tag] = [urlPath]
|
contentMap.tags[tag] = [urlPath]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
addDocument({
|
addDocument({
|
||||||
title: markdownData.title,
|
title: markdownData.title,
|
||||||
body: markdownData.content,
|
body: markdownData.content,
|
||||||
url: urlPath,
|
url: urlPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
contentMap.posts[urlPath] = postData
|
contentMap.posts[urlPath] = postData
|
||||||
|
|
||||||
// series markdown starting with 0 is a series descriptor
|
// series markdown starting with 0 is a series descriptor
|
||||||
if (isFileDescriptor) {
|
if (isFileDescriptor) {
|
||||||
contentMap.series[urlPath] = {
|
contentMap.series[urlPath] = {
|
||||||
...postData,
|
...postData,
|
||||||
order: [],
|
order: [],
|
||||||
length: 0,
|
length: 0,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// put series post in appropriate series
|
// put series post in appropriate series
|
||||||
for (const key of Object.keys(contentMap.series)) {
|
for (const key of Object.keys(contentMap.series)) {
|
||||||
if (urlPath.includes(key)) {
|
if (urlPath.includes(key)) {
|
||||||
const index = parseInt(
|
const index = parseInt(
|
||||||
_urlPath.slice(
|
_urlPath.slice(
|
||||||
_urlPath.lastIndexOf("/") + 1,
|
_urlPath.lastIndexOf("/") + 1,
|
||||||
_urlPath.lastIndexOf("_")
|
_urlPath.lastIndexOf("_")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isNaN(index)) throw Error(`Invalid series index at: ${path}`)
|
if (isNaN(index))
|
||||||
|
throw Error(`Invalid series index at: ${path}`)
|
||||||
|
|
||||||
const itemToPush = {
|
const itemToPush = {
|
||||||
index: index,
|
index: index,
|
||||||
url: urlPath,
|
url: urlPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seriesMap[key]) {
|
if (seriesMap[key]) {
|
||||||
seriesMap[key].push(itemToPush)
|
seriesMap[key].push(itemToPush)
|
||||||
} else {
|
} else {
|
||||||
seriesMap[key] = [itemToPush]
|
seriesMap[key] = [itemToPush]
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save content
|
* Save content
|
||||||
*/
|
*/
|
||||||
|
|
||||||
writeToFile(
|
writeToFile(
|
||||||
`${contentDirectoryPath}${urlPath}.json`,
|
`${contentDirectoryPath}${urlPath}.json`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: markdownData.content,
|
content: markdownData.content,
|
||||||
toc: generateToc(markdownRaw),
|
toc: generateToc(markdownRaw),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,30 +5,30 @@ import { contentMap } from ".."
|
||||||
import { DataToPass } from "."
|
import { DataToPass } from "."
|
||||||
|
|
||||||
export default function parseUnsearchable(data: DataToPass): void {
|
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
|
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
|
||||||
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
|
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
|
||||||
|
|
||||||
addDocument({
|
addDocument({
|
||||||
title: markdownData.title,
|
title: markdownData.title,
|
||||||
body: markdownData.content,
|
body: markdownData.content,
|
||||||
url: urlPath,
|
url: urlPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Parse data that will be written to map.js
|
// Parse data that will be written to map.js
|
||||||
contentMap.unsearchable[urlPath] = {
|
contentMap.unsearchable[urlPath] = {
|
||||||
title: markdownData.title as string,
|
title: markdownData.title as string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save content
|
* Save content
|
||||||
*/
|
*/
|
||||||
|
|
||||||
writeToFile(
|
writeToFile(
|
||||||
`${contentDirectoryPath}/unsearchable${urlPath}.json`,
|
`${contentDirectoryPath}/unsearchable${urlPath}.json`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
content: markdownData.content,
|
content: markdownData.content,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ import elasticlunr from "elasticlunr"
|
||||||
import { searchIndexFilePath } from "./config"
|
import { searchIndexFilePath } from "./config"
|
||||||
|
|
||||||
const elasticlunrIndex = elasticlunr(function () {
|
const elasticlunrIndex = elasticlunr(function () {
|
||||||
this.addField("title" as never)
|
this.addField("title" as never)
|
||||||
this.addField("body" as never)
|
this.addField("body" as never)
|
||||||
this.setRef("url" as never)
|
this.setRef("url" as never)
|
||||||
})
|
})
|
||||||
|
|
||||||
export function addDocument(doc: {
|
export function addDocument(doc: {
|
||||||
title?: unknown
|
title?: unknown
|
||||||
body?: string
|
body?: string
|
||||||
url?: string
|
url?: string
|
||||||
}) {
|
}) {
|
||||||
elasticlunrIndex.addDoc(doc)
|
elasticlunrIndex.addDoc(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveIndex() {
|
export function saveIndex() {
|
||||||
fs.writeFileSync(searchIndexFilePath, JSON.stringify(elasticlunrIndex))
|
fs.writeFileSync(searchIndexFilePath, JSON.stringify(elasticlunrIndex))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
declare module "markdown-it-texmath" {
|
declare module "markdown-it-texmath" {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export default function texmath(md: MarkdownIt, ...params: any[]): void
|
export default function texmath(md: MarkdownIt, ...params: any[]): void
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
declare module "markdown-toc" {
|
declare module "markdown-toc" {
|
||||||
export default function toc(str: string): {
|
export default function toc(str: string): {
|
||||||
json: JSON
|
json: JSON
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
export interface ContentMap {
|
export interface ContentMap {
|
||||||
// key: YYYY-MM-DD
|
// key: YYYY-MM-DD
|
||||||
// value: url
|
// value: url
|
||||||
date: { [key: string]: string[] }
|
date: { [key: string]: string[] }
|
||||||
|
|
||||||
// key: tag name
|
// key: tag name
|
||||||
// value: url
|
// value: url
|
||||||
tags: {
|
tags: {
|
||||||
[key: string]: string[]
|
[key: string]: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// list of all meta data
|
// list of all meta data
|
||||||
meta: {
|
meta: {
|
||||||
tags: string[]
|
tags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchable, non-series posts
|
// searchable, non-series posts
|
||||||
// must have a post date
|
// must have a post date
|
||||||
// tag is not required
|
// tag is not required
|
||||||
posts: {
|
posts: {
|
||||||
[key: string]: PostData
|
[key: string]: PostData
|
||||||
}
|
}
|
||||||
|
|
||||||
// series posts have "previous post" and "next post" button so they need to be ordered
|
// series posts have "previous post" and "next post" button so they need to be ordered
|
||||||
series: { [key: string]: Series }
|
series: { [key: string]: Series }
|
||||||
|
|
||||||
// urls of unsearchable posts
|
// urls of unsearchable posts
|
||||||
// it is here to quickly check if a post exists or not
|
// it is here to quickly check if a post exists or not
|
||||||
unsearchable: { [key: string]: { title: string } }
|
unsearchable: { [key: string]: { title: string } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,58 +34,58 @@ export interface ContentMap {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum ParseMode {
|
export enum ParseMode {
|
||||||
POSTS,
|
POSTS,
|
||||||
SERIES,
|
SERIES,
|
||||||
UNSEARCHABLE,
|
UNSEARCHABLE,
|
||||||
PORTFOLIO,
|
PORTFOLIO,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkdownData {
|
export interface MarkdownData {
|
||||||
content: string
|
content: string
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostData {
|
export interface PostData {
|
||||||
title: string
|
title: string
|
||||||
date: string
|
date: string
|
||||||
readTime: string
|
readTime: string
|
||||||
wordCount: number
|
wordCount: number
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageData {
|
export interface PageData {
|
||||||
title: string
|
title: string
|
||||||
date: string
|
date: string
|
||||||
readTime: string
|
readTime: string
|
||||||
wordCount: number
|
wordCount: number
|
||||||
tags: string[]
|
tags: string[]
|
||||||
toc?: string
|
toc?: string
|
||||||
content: string
|
content: string
|
||||||
|
|
||||||
// series
|
// series
|
||||||
|
|
||||||
seriesHome: string
|
seriesHome: string
|
||||||
prev?: string
|
prev?: string
|
||||||
next?: string
|
next?: string
|
||||||
|
|
||||||
// series home
|
// series home
|
||||||
|
|
||||||
order: string[]
|
order: string[]
|
||||||
length: number
|
length: number
|
||||||
|
|
||||||
// portfolio
|
// portfolio
|
||||||
|
|
||||||
image: string // image url
|
image: string // image url
|
||||||
overview: string
|
overview: string
|
||||||
badges: string[]
|
badges: string[]
|
||||||
repo: string
|
repo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Badge {
|
export interface Badge {
|
||||||
svg: string
|
svg: string
|
||||||
hex: string
|
hex: string
|
||||||
isDark: boolean
|
isDark: boolean
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,23 +93,23 @@ export interface Badge {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface Series {
|
export interface Series {
|
||||||
title: string
|
title: string
|
||||||
date: string
|
date: string
|
||||||
readTime: string
|
readTime: string
|
||||||
wordCount: number
|
wordCount: number
|
||||||
order: string[]
|
order: string[]
|
||||||
length: number
|
length: number
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SeriesMap {
|
export interface SeriesMap {
|
||||||
// key: url
|
// key: url
|
||||||
[key: string]: SeriesEntry[]
|
[key: string]: SeriesEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SeriesEntry {
|
export interface SeriesEntry {
|
||||||
index: number
|
index: number
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,25 +117,25 @@ export interface SeriesEntry {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface PortfolioData {
|
export interface PortfolioData {
|
||||||
// a set of valid simple icons slug
|
// a set of valid simple icons slug
|
||||||
skills: Set<string>
|
skills: Set<string>
|
||||||
|
|
||||||
// key: url
|
// key: url
|
||||||
projects: {
|
projects: {
|
||||||
[key: string]: PortfolioProject
|
[key: string]: PortfolioProject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortfolioOverview {
|
export interface PortfolioOverview {
|
||||||
// link to my github
|
// link to my github
|
||||||
github: string
|
github: string
|
||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortfolioProject {
|
export interface PortfolioProject {
|
||||||
name: string
|
name: string
|
||||||
image: string // url to the image
|
image: string // url to the image
|
||||||
overview: string
|
overview: string
|
||||||
badges: string[] // array of valid simpleIcons slug
|
badges: string[] // array of valid simpleIcons slug
|
||||||
repo: string // url of the git repository
|
repo: string // url of the git repository
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ import { markdownPath } from "./config"
|
||||||
* @param {string} pathToConvert
|
* @param {string} pathToConvert
|
||||||
*/
|
*/
|
||||||
export function path2URL(pathToConvert: string): string {
|
export function path2URL(pathToConvert: string): string {
|
||||||
return `/${relative(markdownPath, pathToConvert)}`
|
return `/${relative(markdownPath, pathToConvert)}`
|
||||||
.replace(/\.[^/.]+$/, "") // remove the file extension
|
.replace(/\.[^/.]+$/, "") // remove the file extension
|
||||||
.replace(/ /g, "-") // replace all space with a dash
|
.replace(/ /g, "-") // replace all space with a dash
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,33 +20,34 @@ export function path2URL(pathToConvert: string): string {
|
||||||
* @param {string} inputPath - path to parse
|
* @param {string} inputPath - path to parse
|
||||||
*/
|
*/
|
||||||
export function path2FileOrFolderName(inputPath: string): string {
|
export function path2FileOrFolderName(inputPath: string): string {
|
||||||
// remove trailing slash
|
// remove trailing slash
|
||||||
if (inputPath[-1] == "/") inputPath = inputPath.slice(0, inputPath.length - 1)
|
if (inputPath[-1] == "/")
|
||||||
|
inputPath = inputPath.slice(0, inputPath.length - 1)
|
||||||
|
|
||||||
// get the last section
|
// get the last section
|
||||||
return inputPath.slice(inputPath.lastIndexOf("/") + 1)
|
return inputPath.slice(inputPath.lastIndexOf("/") + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets the nth occurance of a pattern in string
|
// gets the nth occurance of a pattern in string
|
||||||
// returns -1 if nothing is found
|
// returns -1 if nothing is found
|
||||||
// https://stackoverflow.com/a/14482123/12979111
|
// https://stackoverflow.com/a/14482123/12979111
|
||||||
export function nthIndex(str: string, pat: string, n: number) {
|
export function nthIndex(str: string, pat: string, n: number) {
|
||||||
let i = -1
|
let i = -1
|
||||||
|
|
||||||
while (n-- && i++ < str.length) {
|
while (n-- && i++ < str.length) {
|
||||||
i = str.indexOf(pat, i)
|
i = str.indexOf(pat, i)
|
||||||
if (i < 0) break
|
if (i < 0) break
|
||||||
}
|
}
|
||||||
|
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeToFile(filePath: string, dataToWrite: string) {
|
export function writeToFile(filePath: string, dataToWrite: string) {
|
||||||
// create directory to put the files
|
// create directory to put the files
|
||||||
fs.mkdirSync(filePath.slice(0, filePath.lastIndexOf("/")), {
|
fs.mkdirSync(filePath.slice(0, filePath.lastIndexOf("/")), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// write content to the file
|
// write content to the file
|
||||||
fs.writeFileSync(filePath, dataToWrite)
|
fs.writeFileSync(filePath, dataToWrite)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"extends": "@developomp-site/tsconfig/node16.json",
|
"extends": "@developomp-site/tsconfig/node16.json",
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"esm": true
|
"esm": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"isolatedModules": false,
|
"isolatedModules": false,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"exclude": ["dist", "node_modules"]
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
|
/** @type {import("eslint").Linter.Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: [
|
root: true,
|
||||||
"plugin:@typescript-eslint/recommended",
|
extends: [
|
||||||
"plugin:json/recommended",
|
"eslint:recommended",
|
||||||
"eslint:recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"prettier",
|
"prettier",
|
||||||
"turbo",
|
"turbo",
|
||||||
],
|
],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: ["@typescript-eslint"],
|
plugins: ["@typescript-eslint", "simple-import-sort"],
|
||||||
rules: {
|
rules: {
|
||||||
"@next/next/no-html-link-for-pages": "off",
|
// import related
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"simple-import-sort/exports": "error",
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
"import/first": "error",
|
||||||
},
|
"import/newline-after-import": "error",
|
||||||
|
"import/no-duplicates": "error",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/eslint-config",
|
"name": "@developomp-site/eslint-config",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf node_modules"
|
"clean": "rm -rf node_modules"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.46.0",
|
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||||
"@typescript-eslint/parser": "^5.46.0",
|
"@typescript-eslint/parser": "^5.60.1",
|
||||||
"eslint-config-next": "^12.3.4",
|
"eslint": "^8.43.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-config-turbo": "latest",
|
"eslint-config-turbo": "latest",
|
||||||
"eslint-plugin-json": "^3.1.0"
|
"eslint-plugin-json": "^3.1.0",
|
||||||
}
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/tailwind-config",
|
"name": "@developomp-site/tailwind-config",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf node_modules"
|
"clean": "rm -rf node_modules"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
// app content
|
// app content
|
||||||
`src/**/*.{js,ts,jsx,tsx}`,
|
`src/**/*.{js,ts,jsx,tsx}`,
|
||||||
// include packages if not transpiling
|
// include packages if not transpiling
|
||||||
// "../../packages/**/*.{js,ts,jsx,tsx}",
|
// "../../packages/**/*.{js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"node": true
|
"node": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
252
packages/theme/index.d.ts
vendored
252
packages/theme/index.d.ts
vendored
|
@ -1,143 +1,143 @@
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
font: {
|
font: {
|
||||||
sansSerif: string
|
sansSerif: string
|
||||||
monospace: string
|
monospace: string
|
||||||
}
|
}
|
||||||
|
|
||||||
color: {
|
color: {
|
||||||
text: {
|
text: {
|
||||||
highContrast: string
|
highContrast: string
|
||||||
default: string
|
default: string
|
||||||
gray: string
|
gray: string
|
||||||
}
|
}
|
||||||
background: string
|
background: string
|
||||||
}
|
}
|
||||||
|
|
||||||
maxDisplayWidth: {
|
maxDisplayWidth: {
|
||||||
mobile: string
|
mobile: string
|
||||||
desktop: string
|
desktop: string
|
||||||
}
|
}
|
||||||
|
|
||||||
component: {
|
component: {
|
||||||
anchor: {
|
anchor: {
|
||||||
color: {
|
color: {
|
||||||
default: string
|
default: string
|
||||||
hover: string
|
hover: string
|
||||||
active: string
|
active: string
|
||||||
header: string
|
header: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockQuote: {
|
blockQuote: {
|
||||||
color: {
|
color: {
|
||||||
background: string
|
background: string
|
||||||
borderLeft: string
|
borderLeft: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
card: {
|
card: {
|
||||||
color: {
|
color: {
|
||||||
background: string
|
background: string
|
||||||
hoverGlow: string
|
hoverGlow: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code: {
|
code: {
|
||||||
inline: {
|
inline: {
|
||||||
color: {
|
color: {
|
||||||
text: string
|
text: string
|
||||||
background: string
|
background: string
|
||||||
border: string
|
border: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
block: {
|
block: {
|
||||||
color: {
|
color: {
|
||||||
border: string
|
border: string
|
||||||
highlight: string
|
highlight: string
|
||||||
}
|
}
|
||||||
style: string
|
style: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: {
|
footer: {
|
||||||
color: {
|
color: {
|
||||||
background: string
|
background: string
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header: {
|
header: {
|
||||||
color: {
|
color: {
|
||||||
background: string
|
background: string
|
||||||
hover: string
|
hover: string
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
height: string
|
height: string
|
||||||
}
|
}
|
||||||
|
|
||||||
input: {
|
input: {
|
||||||
color: {
|
color: {
|
||||||
background: {
|
background: {
|
||||||
default: string
|
default: string
|
||||||
itemHover: string
|
itemHover: string
|
||||||
}
|
}
|
||||||
border: {
|
border: {
|
||||||
default: string
|
default: string
|
||||||
hover: string
|
hover: string
|
||||||
focus: string
|
focus: string
|
||||||
}
|
}
|
||||||
placeHolder: string
|
placeHolder: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kbd: {
|
kbd: {
|
||||||
color: {
|
color: {
|
||||||
text: string
|
text: string
|
||||||
border: string
|
border: string
|
||||||
outerShadow: string
|
outerShadow: string
|
||||||
innerShadow: string
|
innerShadow: string
|
||||||
background: string
|
background: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mark: {
|
mark: {
|
||||||
color: {
|
color: {
|
||||||
text: string
|
text: string
|
||||||
background: string
|
background: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
color: {
|
color: {
|
||||||
track: string
|
track: string
|
||||||
thumb: string
|
thumb: string
|
||||||
}
|
}
|
||||||
width: string
|
width: string
|
||||||
borderRadius: string
|
borderRadius: string
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollProgressBar: {
|
scrollProgressBar: {
|
||||||
color: {
|
color: {
|
||||||
background: string
|
background: string
|
||||||
foreground: string
|
foreground: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table: {
|
table: {
|
||||||
color: {
|
color: {
|
||||||
border: string
|
border: string
|
||||||
even: string
|
even: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
color: {
|
color: {
|
||||||
background: {
|
background: {
|
||||||
default: string
|
default: string
|
||||||
hover: string
|
hover: string
|
||||||
}
|
}
|
||||||
border: string
|
border: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/theme",
|
"name": "@developomp-site/theme",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"types": "./index.d.ts",
|
"types": "./index.d.ts",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --ignore dist/ --exec pnpm build",
|
"dev": "nodemon --ignore dist/ --exec pnpm build",
|
||||||
"build": "npx ts-node ./build.ts",
|
"build": "npx ts-node ./build.ts",
|
||||||
"clean": "rm -rf .turbo node_modules dist"
|
"clean": "rm -rf .turbo node_modules dist"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/merge-deep": "^3.0.0",
|
"@types/merge-deep": "^3.0.0",
|
||||||
"@types/node": "^18.11.11",
|
"@types/node": "^18.11.11",
|
||||||
"merge-deep": "^3.0.3",
|
"merge-deep": "^3.0.3",
|
||||||
"nodemon": "^2.0.20",
|
"nodemon": "^2.0.20",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.2.4",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsup": "^5.12.9",
|
"tsup": "^5.12.9",
|
||||||
"utility-types": "^3.10.0"
|
"utility-types": "^3.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,71 @@
|
||||||
/* from highlight.js/styles/atom-one-dark-reasonable.css */
|
/* from highlight.js/styles/atom-one-dark-reasonable.css */
|
||||||
|
|
||||||
pre code.hljs {
|
pre code.hljs {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
code.hljs {
|
code.hljs {
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
}
|
}
|
||||||
.hljs {
|
.hljs {
|
||||||
color: #abb2bf;
|
color: #abb2bf;
|
||||||
background: #282c34;
|
background: #282c34;
|
||||||
}
|
}
|
||||||
.hljs-keyword,
|
.hljs-keyword,
|
||||||
.hljs-operator,
|
.hljs-operator,
|
||||||
.hljs-pattern-match {
|
.hljs-pattern-match {
|
||||||
color: #f92672;
|
color: #f92672;
|
||||||
}
|
}
|
||||||
.hljs-function,
|
.hljs-function,
|
||||||
.hljs-pattern-match .hljs-constructor {
|
.hljs-pattern-match .hljs-constructor {
|
||||||
color: #61aeee;
|
color: #61aeee;
|
||||||
}
|
}
|
||||||
.hljs-function .hljs-params {
|
.hljs-function .hljs-params {
|
||||||
color: #a6e22e;
|
color: #a6e22e;
|
||||||
}
|
}
|
||||||
.hljs-function .hljs-params .hljs-typing {
|
.hljs-function .hljs-params .hljs-typing {
|
||||||
color: #fd971f;
|
color: #fd971f;
|
||||||
}
|
}
|
||||||
.hljs-module-access .hljs-module {
|
.hljs-module-access .hljs-module {
|
||||||
color: #7e57c2;
|
color: #7e57c2;
|
||||||
}
|
}
|
||||||
.hljs-constructor {
|
.hljs-constructor {
|
||||||
color: #e2b93d;
|
color: #e2b93d;
|
||||||
}
|
}
|
||||||
.hljs-constructor .hljs-string {
|
.hljs-constructor .hljs-string {
|
||||||
color: #9ccc65;
|
color: #9ccc65;
|
||||||
}
|
}
|
||||||
.hljs-comment,
|
.hljs-comment,
|
||||||
.hljs-quote {
|
.hljs-quote {
|
||||||
color: #b18eb1;
|
color: #b18eb1;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.hljs-doctag,
|
.hljs-doctag,
|
||||||
.hljs-formula {
|
.hljs-formula {
|
||||||
color: #c678dd;
|
color: #c678dd;
|
||||||
}
|
}
|
||||||
.hljs-deletion,
|
.hljs-deletion,
|
||||||
.hljs-name,
|
.hljs-name,
|
||||||
.hljs-section,
|
.hljs-section,
|
||||||
.hljs-selector-tag,
|
.hljs-selector-tag,
|
||||||
.hljs-subst {
|
.hljs-subst {
|
||||||
color: #e06c75;
|
color: #e06c75;
|
||||||
}
|
}
|
||||||
.hljs-literal {
|
.hljs-literal {
|
||||||
color: #56b6c2;
|
color: #56b6c2;
|
||||||
}
|
}
|
||||||
.hljs-addition,
|
.hljs-addition,
|
||||||
.hljs-attribute,
|
.hljs-attribute,
|
||||||
.hljs-meta .hljs-string,
|
.hljs-meta .hljs-string,
|
||||||
.hljs-regexp,
|
.hljs-regexp,
|
||||||
.hljs-string {
|
.hljs-string {
|
||||||
color: #98c379;
|
color: #98c379;
|
||||||
}
|
}
|
||||||
.hljs-built_in,
|
.hljs-built_in,
|
||||||
.hljs-class .hljs-title,
|
.hljs-class .hljs-title,
|
||||||
.hljs-title.class_ {
|
.hljs-title.class_ {
|
||||||
color: #e6c07b;
|
color: #e6c07b;
|
||||||
}
|
}
|
||||||
.hljs-attr,
|
.hljs-attr,
|
||||||
.hljs-number,
|
.hljs-number,
|
||||||
|
@ -75,7 +75,7 @@ code.hljs {
|
||||||
.hljs-template-variable,
|
.hljs-template-variable,
|
||||||
.hljs-type,
|
.hljs-type,
|
||||||
.hljs-variable {
|
.hljs-variable {
|
||||||
color: #d19a66;
|
color: #d19a66;
|
||||||
}
|
}
|
||||||
.hljs-bullet,
|
.hljs-bullet,
|
||||||
.hljs-link,
|
.hljs-link,
|
||||||
|
@ -83,14 +83,14 @@ code.hljs {
|
||||||
.hljs-selector-id,
|
.hljs-selector-id,
|
||||||
.hljs-symbol,
|
.hljs-symbol,
|
||||||
.hljs-title {
|
.hljs-title {
|
||||||
color: #61aeee;
|
color: #61aeee;
|
||||||
}
|
}
|
||||||
.hljs-emphasis {
|
.hljs-emphasis {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.hljs-strong {
|
.hljs-strong {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.hljs-link {
|
.hljs-link {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,165 +3,145 @@ import type { Theme } from "../.."
|
||||||
import { readFileSync } from "fs"
|
import { readFileSync } from "fs"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
font: {
|
font: {
|
||||||
sansSerif: "'Noto Sans KR', sans-serif", // https://fonts.google.com/noto/specimen/Noto+Sans+KR
|
sansSerif: "'Noto Sans KR', sans-serif", // https://fonts.google.com/noto/specimen/Noto+Sans+KR
|
||||||
monospace: "'Source Code Pro', monospace",
|
monospace: "'Source Code Pro', monospace",
|
||||||
},
|
},
|
||||||
|
|
||||||
color: {
|
color: {
|
||||||
text: {
|
text: {
|
||||||
highContrast: "#FFFFFF",
|
highContrast: "#FFFFFF",
|
||||||
default: "#EEEEEE",
|
default: "#EEEEEE",
|
||||||
gray: "#CCC",
|
gray: "#CCC",
|
||||||
},
|
},
|
||||||
background: "#36393F",
|
background: "#36393F",
|
||||||
},
|
},
|
||||||
|
|
||||||
maxDisplayWidth: {
|
maxDisplayWidth: {
|
||||||
mobile: "1024px", // max-w-screen-lg
|
mobile: "1024px", // max-w-screen-lg
|
||||||
desktop: "1536px", // max-w-screen-2xl
|
desktop: "1536px", // max-w-screen-2xl
|
||||||
},
|
},
|
||||||
|
|
||||||
component: {
|
component: {
|
||||||
anchor: {
|
anchor: {
|
||||||
color: {
|
color: {
|
||||||
default: "#66AAFF",
|
default: "#66AAFF",
|
||||||
hover: "#4592F7",
|
hover: "#4592F7",
|
||||||
active: "#4592F7",
|
active: "#4592F7",
|
||||||
header: "#778899",
|
header: "#778899",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
blockQuote: {
|
blockQuote: {
|
||||||
color: {
|
color: {
|
||||||
background: "#FFFFFF12",
|
background: "#FFFFFF12",
|
||||||
borderLeft: "#FFFFFF4D",
|
borderLeft: "#FFFFFF4D",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
card: {
|
card: {
|
||||||
color: {
|
color: {
|
||||||
background: "#2F3136",
|
background: "#2F3136",
|
||||||
hoverGlow: "#FFFFFF33",
|
hoverGlow: "#FFFFFF33",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
code: {
|
code: {
|
||||||
inline: {
|
inline: {
|
||||||
color: {
|
color: {
|
||||||
text: "#FFFFFF",
|
text: "#FFFFFF",
|
||||||
background: "#444",
|
background: "#444",
|
||||||
border: "#666",
|
border: "#666",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
block: {
|
block: {
|
||||||
color: {
|
color: {
|
||||||
border: "#555",
|
border: "#555",
|
||||||
highlight: "#14161A",
|
highlight: "#14161A",
|
||||||
},
|
},
|
||||||
style: readFileSync(__dirname + "/codeblock.css", "utf-8"),
|
style: readFileSync(__dirname + "/codeblock.css", "utf-8"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
footer: {
|
footer: {
|
||||||
color: {
|
color: {
|
||||||
background: "#000000",
|
background: "#000000",
|
||||||
text: "",
|
text: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
header: {
|
header: {
|
||||||
color: {
|
color: {
|
||||||
background: "#202225", // custom
|
background: "#202225", // custom
|
||||||
hover: "#3F3F46", // zinc-700
|
hover: "#3F3F46", // zinc-700
|
||||||
text: "#D4D4D8", // zinc-300
|
text: "#D4D4D8", // zinc-300
|
||||||
},
|
},
|
||||||
height: "16px", // h-4
|
height: "16px", // h-4
|
||||||
},
|
},
|
||||||
|
|
||||||
input: {
|
input: {
|
||||||
color: {
|
color: {
|
||||||
background: {
|
background: {
|
||||||
default: "#36393f",
|
default: "#36393f",
|
||||||
itemHover: "#202225",
|
itemHover: "#202225",
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
default: "#555555",
|
default: "#555555",
|
||||||
hover: "#808080",
|
hover: "#808080",
|
||||||
focus: "#a3a3a3", // neutral-400
|
focus: "#a3a3a3", // neutral-400
|
||||||
},
|
},
|
||||||
placeHolder: "#A9A9A9",
|
placeHolder: "#A9A9A9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
kbd: {
|
kbd: {
|
||||||
color: {
|
color: {
|
||||||
text: "#FFFFFF",
|
text: "#FFFFFF",
|
||||||
border: "#555555",
|
border: "#555555",
|
||||||
outerShadow: "#FFFFFF4D",
|
outerShadow: "#FFFFFF4D",
|
||||||
innerShadow: "#000000",
|
innerShadow: "#000000",
|
||||||
background: "#000000",
|
background: "#000000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mark: {
|
mark: {
|
||||||
color: {
|
color: {
|
||||||
text: "#FFFFFF",
|
text: "#FFFFFF",
|
||||||
background: "#FFFF0080",
|
background: "#FFFF0080",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
color: {
|
color: {
|
||||||
track: "#18181B",
|
track: "#18181B",
|
||||||
thumb: "#888888",
|
thumb: "#888888",
|
||||||
},
|
},
|
||||||
width: "8px", // w-2
|
width: "8px", // w-2
|
||||||
borderRadius: "4px", // rounded
|
borderRadius: "4px", // rounded
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollProgressBar: {
|
scrollProgressBar: {
|
||||||
color: {
|
color: {
|
||||||
background: "#52525B", // zinc 600
|
background: "#52525B", // zinc 600
|
||||||
foreground: "#D4D4D8", // zinc-300
|
foreground: "#D4D4D8", // zinc-300
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
table: {
|
table: {
|
||||||
color: {
|
color: {
|
||||||
border: "#777777",
|
border: "#777777",
|
||||||
even: "#21272E",
|
even: "#21272E",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
color: {
|
color: {
|
||||||
background: {
|
background: {
|
||||||
default: "#202225",
|
default: "#202225",
|
||||||
hover: "#3F3F46", // zinc-700
|
hover: "#3F3F46", // zinc-700
|
||||||
},
|
},
|
||||||
border: "#555",
|
border: "#555",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as Theme
|
} as Theme
|
||||||
|
|
||||||
/*
|
|
||||||
dark: {
|
|
||||||
backgroundColor0: "#18181b",
|
|
||||||
backgroundColor1: "#36393F",
|
|
||||||
backgroundColor2: "#2F3136",
|
|
||||||
color0: "#FFFFFF",
|
|
||||||
color1: "#EEEEEE",
|
|
||||||
color2: "#CCC",
|
|
||||||
}
|
|
||||||
|
|
||||||
light: {
|
|
||||||
backgroundColor0: "#FFFFFF",
|
|
||||||
backgroundColor1: "#F7F7F7",
|
|
||||||
backgroundColor2: "#DDDDDD",
|
|
||||||
color0: "#000000",
|
|
||||||
color1: "#111111",
|
|
||||||
color2: "#555",
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
/* from highlight.js/styles/default.css */
|
/* from highlight.js/styles/default.css */
|
||||||
|
|
||||||
pre code.hljs {
|
pre code.hljs {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
code.hljs {
|
code.hljs {
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
}
|
}
|
||||||
.hljs {
|
.hljs {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
color: #444;
|
color: #444;
|
||||||
}
|
}
|
||||||
.hljs-comment {
|
.hljs-comment {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
.hljs-punctuation,
|
.hljs-punctuation,
|
||||||
.hljs-tag {
|
.hljs-tag {
|
||||||
color: #444a;
|
color: #444a;
|
||||||
}
|
}
|
||||||
.hljs-tag .hljs-attr,
|
.hljs-tag .hljs-attr,
|
||||||
.hljs-tag .hljs-name {
|
.hljs-tag .hljs-name {
|
||||||
color: #444;
|
color: #444;
|
||||||
}
|
}
|
||||||
.hljs-attribute,
|
.hljs-attribute,
|
||||||
.hljs-doctag,
|
.hljs-doctag,
|
||||||
|
@ -29,7 +29,7 @@ code.hljs {
|
||||||
.hljs-meta .hljs-keyword,
|
.hljs-meta .hljs-keyword,
|
||||||
.hljs-name,
|
.hljs-name,
|
||||||
.hljs-selector-tag {
|
.hljs-selector-tag {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.hljs-deletion,
|
.hljs-deletion,
|
||||||
.hljs-number,
|
.hljs-number,
|
||||||
|
@ -39,12 +39,12 @@ code.hljs {
|
||||||
.hljs-string,
|
.hljs-string,
|
||||||
.hljs-template-tag,
|
.hljs-template-tag,
|
||||||
.hljs-type {
|
.hljs-type {
|
||||||
color: #800;
|
color: #800;
|
||||||
}
|
}
|
||||||
.hljs-section,
|
.hljs-section,
|
||||||
.hljs-title {
|
.hljs-title {
|
||||||
color: #800;
|
color: #800;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.hljs-link,
|
.hljs-link,
|
||||||
.hljs-operator,
|
.hljs-operator,
|
||||||
|
@ -54,26 +54,26 @@ code.hljs {
|
||||||
.hljs-symbol,
|
.hljs-symbol,
|
||||||
.hljs-template-variable,
|
.hljs-template-variable,
|
||||||
.hljs-variable {
|
.hljs-variable {
|
||||||
color: #bc6060;
|
color: #bc6060;
|
||||||
}
|
}
|
||||||
.hljs-literal {
|
.hljs-literal {
|
||||||
color: #78a960;
|
color: #78a960;
|
||||||
}
|
}
|
||||||
.hljs-addition,
|
.hljs-addition,
|
||||||
.hljs-built_in,
|
.hljs-built_in,
|
||||||
.hljs-bullet,
|
.hljs-bullet,
|
||||||
.hljs-code {
|
.hljs-code {
|
||||||
color: #397300;
|
color: #397300;
|
||||||
}
|
}
|
||||||
.hljs-meta {
|
.hljs-meta {
|
||||||
color: #1f7199;
|
color: #1f7199;
|
||||||
}
|
}
|
||||||
.hljs-meta .hljs-string {
|
.hljs-meta .hljs-string {
|
||||||
color: #4d99bf;
|
color: #4d99bf;
|
||||||
}
|
}
|
||||||
.hljs-emphasis {
|
.hljs-emphasis {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.hljs-strong {
|
.hljs-strong {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,120 +7,120 @@ import { DeepPartial } from "utility-types"
|
||||||
import BaseTheme from "../dark"
|
import BaseTheme from "../dark"
|
||||||
|
|
||||||
export default merge<Theme, DeepPartial<Theme>>(BaseTheme, {
|
export default merge<Theme, DeepPartial<Theme>>(BaseTheme, {
|
||||||
color: {
|
color: {
|
||||||
text: {
|
text: {
|
||||||
highContrast: "#000000",
|
highContrast: "#000000",
|
||||||
default: "#111111",
|
default: "#111111",
|
||||||
gray: "#555",
|
gray: "#555",
|
||||||
},
|
},
|
||||||
background: "#F7F7F7",
|
background: "#F7F7F7",
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
anchor: {
|
anchor: {
|
||||||
color: {
|
color: {
|
||||||
header: "#D3D3D3",
|
header: "#D3D3D3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
blockQuote: {
|
blockQuote: {
|
||||||
color: {
|
color: {
|
||||||
background: "#0000000D",
|
background: "#0000000D",
|
||||||
borderLeft: "#0000001A",
|
borderLeft: "#0000001A",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
card: {
|
card: {
|
||||||
color: {
|
color: {
|
||||||
background: "#FFFFFF",
|
background: "#FFFFFF",
|
||||||
hoverGlow: "#00000040",
|
hoverGlow: "#00000040",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
code: {
|
code: {
|
||||||
inline: {
|
inline: {
|
||||||
color: {
|
color: {
|
||||||
text: "#000000",
|
text: "#000000",
|
||||||
background: "#EEE",
|
background: "#EEE",
|
||||||
border: "#BBB",
|
border: "#BBB",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
block: {
|
block: {
|
||||||
color: {
|
color: {
|
||||||
border: "#BBB",
|
border: "#BBB",
|
||||||
highlight: "#DDDDDD",
|
highlight: "#DDDDDD",
|
||||||
},
|
},
|
||||||
style: readFileSync(__dirname + "/codeblock.css", "utf-8"),
|
style: readFileSync(__dirname + "/codeblock.css", "utf-8"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
footer: {
|
footer: {
|
||||||
color: {
|
color: {
|
||||||
background: "#FFFFFF",
|
background: "#FFFFFF",
|
||||||
text: "",
|
text: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
input: {
|
input: {
|
||||||
color: {
|
color: {
|
||||||
background: {
|
background: {
|
||||||
default: "#EEEEEE",
|
default: "#EEEEEE",
|
||||||
itemHover: "#FFFFFF",
|
itemHover: "#FFFFFF",
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
default: "#CCCCCC",
|
default: "#CCCCCC",
|
||||||
hover: "#808080",
|
hover: "#808080",
|
||||||
focus: "#000000",
|
focus: "#000000",
|
||||||
},
|
},
|
||||||
placeHolder: "#777777",
|
placeHolder: "#777777",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
kbd: {
|
kbd: {
|
||||||
color: {
|
color: {
|
||||||
text: "#333333",
|
text: "#333333",
|
||||||
border: "#CCCCCC",
|
border: "#CCCCCC",
|
||||||
outerShadow: "#00000033",
|
outerShadow: "#00000033",
|
||||||
innerShadow: "#FFFFFF",
|
innerShadow: "#FFFFFF",
|
||||||
background: "#F7F7F7",
|
background: "#F7F7F7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mark: {
|
mark: {
|
||||||
color: {
|
color: {
|
||||||
text: "#000000",
|
text: "#000000",
|
||||||
background: "#FFFF00BF",
|
background: "#FFFF00BF",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
color: {
|
color: {
|
||||||
track: "#FFFFFF",
|
track: "#FFFFFF",
|
||||||
thumb: "#DDDDDD",
|
thumb: "#DDDDDD",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollProgressBar: {
|
scrollProgressBar: {
|
||||||
color: {
|
color: {
|
||||||
background: "#d4d4d8", // zinc-300
|
background: "#d4d4d8", // zinc-300
|
||||||
foreground: "#52525b", // zinc-600
|
foreground: "#52525b", // zinc-600
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
table: {
|
table: {
|
||||||
color: {
|
color: {
|
||||||
border: "#DDD",
|
border: "#DDD",
|
||||||
even: "#F2F2F2",
|
even: "#F2F2F2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
color: {
|
color: {
|
||||||
background: {
|
background: {
|
||||||
default: "#FFFFFF",
|
default: "#FFFFFF",
|
||||||
hover: "#EEEEEE",
|
hover: "#EEEEEE",
|
||||||
},
|
},
|
||||||
border: "#CCC",
|
border: "#CCC",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as Theme
|
}) as Theme
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["dist", "node_modules"]
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"display": "Default",
|
"display": "Default",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"inlineSources": false,
|
"inlineSources": false,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"display": "Node 16",
|
"display": "Node 16",
|
||||||
"extends": "./base.json",
|
"extends": "./base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ES2020"],
|
"lib": ["ES2020"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "ES2020"
|
"target": "ES2020"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/tsconfig",
|
"name": "@developomp-site/tsconfig",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"display": "React Library",
|
"display": "React Library",
|
||||||
"extends": "./base.json",
|
"extends": "./base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"lib": ["dom", "ES2015"],
|
"lib": ["dom", "ES2015"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "es6"
|
"target": "es6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: ["developomp-site"],
|
extends: ["developomp-site"],
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
{
|
{
|
||||||
"name": "@developomp-site/utils",
|
"name": "@developomp-site/utils",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**"
|
"dist/**"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup src/index.tsx --format esm,cjs --dts --external react",
|
"build": "tsup src/index.tsx --format esm,cjs --dts --external react",
|
||||||
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
|
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
|
||||||
"lint": "TIMING=1 eslint \"src/**/*.ts*\"",
|
"lint": "TIMING=1 eslint \"src/**/*.ts*\"",
|
||||||
"clean": "rm -rf .turbo node_modules dist"
|
"clean": "rm -rf .turbo node_modules dist"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@developomp-site/eslint-config": "workspace:*",
|
"@developomp-site/eslint-config": "workspace:*",
|
||||||
"@developomp-site/tsconfig": "workspace:*",
|
"@developomp-site/tsconfig": "workspace:*",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"eslint": "^8.29.0",
|
"eslint": "^8.29.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"tsup": "^5.12.9",
|
"tsup": "^5.12.9",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "@developomp-site/tsconfig/react-library.json",
|
"extends": "@developomp-site/tsconfig/react-library.json",
|
||||||
"include": ["."],
|
"include": ["."],
|
||||||
"exclude": ["dist", "build", "node_modules"]
|
"exclude": ["dist", "build", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue