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