refactor: remove localization
Why: - I was doing half-ass job at it in the first place (code-wise) - maintaining sites for more than one locale proved to be more difficult than initially anticipated - having more than one local does not add much value - overall not worth it
This commit is contained in:
parent
e26738ee07
commit
4404518359
30 changed files with 99 additions and 691 deletions
|
@ -9,25 +9,6 @@ import { DataToPass } from "."
|
||||||
export default function parsePortfolio(data: DataToPass): void {
|
export default function parsePortfolio(data: DataToPass): void {
|
||||||
const { urlPath, markdownRaw, markdownData } = data
|
const { urlPath, markdownRaw, markdownData } = data
|
||||||
|
|
||||||
if (urlPath.endsWith(".kr")) {
|
|
||||||
const contentID = urlPath.slice(0, urlPath.length - 3)
|
|
||||||
|
|
||||||
if (portfolioData.projects[contentID]) {
|
|
||||||
portfolioData.projects[contentID] = {
|
|
||||||
...portfolioData.projects[contentID],
|
|
||||||
overview_kr: markdownData.overview as string,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
portfolioData.projects[contentID] = {
|
|
||||||
name: "",
|
|
||||||
image: "",
|
|
||||||
overview_en: "",
|
|
||||||
overview_kr: markdownData.overview as string,
|
|
||||||
badges: [],
|
|
||||||
repo: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (markdownData.badges) {
|
if (markdownData.badges) {
|
||||||
;(markdownData.badges as string[]).forEach((slug) => {
|
;(markdownData.badges as string[]).forEach((slug) => {
|
||||||
// todo: handle cases when icon is not on simple-icons
|
// todo: handle cases when icon is not on simple-icons
|
||||||
|
@ -55,14 +36,10 @@ export default function parsePortfolio(data: DataToPass): void {
|
||||||
portfolioData.projects[urlPath] = {
|
portfolioData.projects[urlPath] = {
|
||||||
name: markdownData.name as string,
|
name: markdownData.name as string,
|
||||||
image: markdownData.image as string,
|
image: markdownData.image as string,
|
||||||
overview_en: markdownData.overview as string,
|
overview: markdownData.overview as string,
|
||||||
overview_kr: portfolioData.projects[urlPath]
|
|
||||||
? portfolioData.projects[urlPath].overview_kr
|
|
||||||
: "",
|
|
||||||
badges: (markdownData.badges as string[]) || [],
|
badges: (markdownData.badges as string[]) || [],
|
||||||
repo: (markdownData.repo as string) || "",
|
repo: (markdownData.repo as string) || "",
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
writeToFile(
|
writeToFile(
|
||||||
`${contentDirectoryPath}${urlPath}.json`,
|
`${contentDirectoryPath}${urlPath}.json`,
|
||||||
|
|
|
@ -10,7 +10,6 @@ export default function parseUnsearchable(data: DataToPass): void {
|
||||||
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
|
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
|
||||||
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
|
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
|
||||||
|
|
||||||
if (!urlPath.endsWith(".kr.md")) {
|
|
||||||
addDocument({
|
addDocument({
|
||||||
title: markdownData.title,
|
title: markdownData.title,
|
||||||
body: markdownData.content,
|
body: markdownData.content,
|
||||||
|
@ -21,7 +20,6 @@ export default function parseUnsearchable(data: DataToPass): void {
|
||||||
map.unsearchable[urlPath] = {
|
map.unsearchable[urlPath] = {
|
||||||
title: markdownData.title as string,
|
title: markdownData.title as string,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save content
|
* Save content
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
---
|
|
||||||
overview: 유니티 게임을 위한 모드. 인-게임 UI와 OBS 오버레이를 제공.
|
|
||||||
---
|
|
||||||
|
|
||||||
## 개요
|
|
||||||
|
|
||||||
War Brokers Mods, 줄여서 WBM은 게임 [War Brokers](https://warbrokers.io)를 위한 모드입니다.
|
|
||||||
|
|
||||||
## 모드
|
|
||||||
|
|
||||||
C#으로 만들어졌으며, [BepInEx](https://github.com/BepInEx/BepInEx) 프레임워크를 사용하여 게임의 여러 측면을 패치합니다.
|
|
||||||
|
|
||||||
## OBS 오버레이
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img alt="OBS 오버레이" src="/img/portfolio/wbm-overlays.png" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[OBS 스튜디오](https://github.com/obsproject/obs-studio)를 위한 오버레이.
|
|
||||||
웹 기술을 이용하여 제작되었습니다 (자바스크립트, CSS, HTML 등).
|
|
||||||
|
|
||||||
## 설치기
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img alt="설치기" src="/img/portfolio/wbm-installer.png" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
간단한 크로스 플랫폼 설칙 및 업데이트 관리기.
|
|
||||||
[tauri](https://github.com/tauri-apps/tauri), [rust](https://github.com/rust-lang/rust), [svelte](https://github.com/sveltejs/svelte), 그리고 [tailwind css](https://github.com/tailwindlabs/tailwindcss)를 사용해 만들어졌습니다.
|
|
|
@ -1,29 +0,0 @@
|
||||||
---
|
|
||||||
title: 소개
|
|
||||||
---
|
|
||||||
|
|
||||||
## 누구세요?
|
|
||||||
|
|
||||||
이름: 김지민<br />
|
|
||||||
거주지: 서울, 대한만국<br />
|
|
||||||
출생: 2002년<br />
|
|
||||||
MBTI: [논리적인 사색가 INTP-A](https://www.16personalities.com/ko/성격유형-intp)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img alt="MBTI result" src="/img/mbti.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 100%; width: 600px" />
|
|
||||||
2023년 1월 2일에 <a href="https://16personalities.com">16personalities.com</a> 검사 결과
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## 링크
|
|
||||||
|
|
||||||
- [깃허브](https://github.com/developomp)
|
|
||||||
- [목표](/goals)
|
|
||||||
|
|
||||||
## 연락
|
|
||||||
|
|
||||||
아래 기재된 방법 외에 다른 방법으로 연락을 할 시 답변을 드리지 못 할 수 있습니다.
|
|
||||||
|
|
||||||
| 플랫폼 | 아이디 | 답변 시간 |
|
|
||||||
| --------------------------------: | :----------------------------------: | :-------- |
|
|
||||||
| [디스코드](https://discord.com) | developomp#0001 (501277805540147220) | 즉각 |
|
|
||||||
| [지메일](https://mail.google.com) | developomp@gmail.com | 2~4일 |
|
|
|
@ -1,61 +0,0 @@
|
||||||
---
|
|
||||||
title: 목표
|
|
||||||
---
|
|
||||||
|
|
||||||
## 프로그래밍
|
|
||||||
|
|
||||||
- 깃허브에서 별 X개 얻기 (본인 미포함)
|
|
||||||
- [x] 10
|
|
||||||
- [ ] 50
|
|
||||||
- [ ] 100
|
|
||||||
- [ ] 500
|
|
||||||
- [ ] 1,000
|
|
||||||
- 깃허브에서 1년에 X개의 기여하기 (1월 1일 ~ 12월 31일)
|
|
||||||
- [x] 1000 ([2021](https://github.com/developomp?tab=overview&from=2021-12-01&to=2021-12-31))
|
|
||||||
- [x] 2000 ([2022](https://github.com/developomp?tab=overview&from=2022-12-01&to=2022-12-31))
|
|
||||||
- [ ] 3000
|
|
||||||
- 알고리즘 문제풀이 ([solved.ac](https://solved.ac))
|
|
||||||
- 모든 X 문제 풀기
|
|
||||||
- [x] [브론즈 V](https://solved.ac/problems/level/1)
|
|
||||||
- [ ] [브론즈 IV](https://solved.ac/problems/level/2)
|
|
||||||
- X티어 달성하기
|
|
||||||
- [x] 브론즈
|
|
||||||
- [x] 실버
|
|
||||||
- [x] 골드 (현재 골드 IV)
|
|
||||||
- [ ] 플래티넘
|
|
||||||
- [ ] 다이아
|
|
||||||
- ~~루비~~
|
|
||||||
- X자리수 랭킹 달성하기
|
|
||||||
- [ ] 4
|
|
||||||
- [ ] 3
|
|
||||||
|
|
||||||
## 기술
|
|
||||||
|
|
||||||
- 분당 X타 치기 (규칙): 일분동안 타자를 친 후 맞은 타의 개수를 센다. 테스트는 [10fastfingers.com](https://10fastfingers.com/typing-test)를 통해 진행한다. 타자 속도는 일관성이 있어야 하며 요구시 같은 결과를 낼 수 있어야 한다.)
|
|
||||||
- 한국어
|
|
||||||
- [x] 100
|
|
||||||
- [x] 150
|
|
||||||
- [x] 200
|
|
||||||
- [ ] 250
|
|
||||||
- [ ] 300
|
|
||||||
- 영어
|
|
||||||
- [x] 100
|
|
||||||
- [x] 150
|
|
||||||
- [x] 200
|
|
||||||
- [x] 250
|
|
||||||
- [ ] 300
|
|
||||||
- [x] 키보드 안보고 타자치기
|
|
||||||
|
|
||||||
## Etc
|
|
||||||
|
|
||||||
- [ ] 유튜브에 만족할만한 퀄리티의 100만 조회수 이상의 영상 올리기
|
|
||||||
- [ ] 취직
|
|
||||||
- [ ] FAANG (또는 이에 준하는) 기업에 취직
|
|
||||||
- [ ] 집 구매
|
|
||||||
- [ ] 기술적 특이점 목격하기
|
|
||||||
- [ ] 달에 가기
|
|
||||||
- X번째 생일 축하하기
|
|
||||||
- [x] 15
|
|
||||||
- [x] 20
|
|
||||||
- [ ] 25
|
|
||||||
- [ ] 30
|
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
title: 이력서
|
|
||||||
---
|
|
||||||
|
|
||||||
## 김지민
|
|
||||||
|
|
||||||
[](https://github.com/developomp)
|
|
||||||
[](/portfolio)
|
|
||||||
|
|
||||||
프론트엔드 엔지니어 지망생
|
|
||||||
|
|
||||||
무엇이든지 직접 만들어보아야 직성이 풀리고 궁금한 것이 있으면 나사 하나,
|
|
||||||
전선 하나까지 뜯어서 원리를 이해하기 전까지 만족할 줄 모르는 천성 개발자 김지민입니다.
|
|
||||||
|
|
||||||
특징:
|
|
||||||
|
|
||||||
- [아치 리눅스](https://archlinux.org)를 사용중 ([깃허브](https://github.com/developomp/setup))
|
|
||||||
- 한국어와 영어를 원어민 수준으로 구사 할 수 있음
|
|
||||||
|
|
||||||
이메일: developomp@gmail.com
|
|
||||||
|
|
||||||
## 교육
|
|
||||||
|
|
||||||
### [홍익대학교](https://wwwce.hongik.ac.kr) 컴퓨터공학과
|
|
||||||
|
|
||||||
- 2022년 3월 - 현재
|
|
||||||
|
|
||||||
## 깃허브
|
|
||||||
|
|
||||||
<img alt="github metrics" src="https://raw.githubusercontent.com/developomp/developomp/master/github-metrics.svg" style="display: block; margin-left: auto; margin-right: auto; max-width: 100%;">
|
|
||||||
|
|
||||||
## 기술
|
|
||||||
|
|
||||||
<img alt="programming skills" src="/img/skills.svg" style="display: block; margin-left: auto; margin-right: auto; max-width: 100%;" />
|
|
|
@ -2,7 +2,7 @@ import darkTheme from "@developomp-site/theme/dist/dark.json"
|
||||||
import lightTheme from "@developomp-site/theme/dist/light.json"
|
import lightTheme from "@developomp-site/theme/dist/light.json"
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from "react"
|
import { useContext, useEffect, useState } from "react"
|
||||||
import { Routes, Route, useNavigate, useLocation } from "react-router-dom"
|
import { Routes, Route } from "react-router-dom"
|
||||||
import styled, { ThemeProvider } from "styled-components"
|
import styled, { ThemeProvider } from "styled-components"
|
||||||
import { Helmet } from "react-helmet-async"
|
import { Helmet } from "react-helmet-async"
|
||||||
import { isIE } from "react-device-detect"
|
import { isIE } from "react-device-detect"
|
||||||
|
@ -37,18 +37,9 @@ const StyledContentContainer = styled.div`
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { globalState } = useContext(globalContext)
|
const { globalState } = useContext(globalContext)
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const { pathname } = useLocation()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
// update url on locale change
|
|
||||||
useEffect(() => {
|
|
||||||
navigate(locale + pathname.slice(3))
|
|
||||||
}, [locale])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// set loading to false if all fonts are loaded
|
// set loading to false if all fonts are loaded
|
||||||
// checks if document.fonts.onloadingdone is supported on the browser
|
// checks if document.fonts.onloadingdone is supported on the browser
|
||||||
|
@ -59,10 +50,6 @@ export default function App() {
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// automatically add locale prefix if it's not specified
|
|
||||||
if (!pathname.startsWith("/en") && !pathname.startsWith("/kr"))
|
|
||||||
navigate(`/${globalState.locale}${pathname}`)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (isIE)
|
if (isIE)
|
||||||
|
@ -93,29 +80,12 @@ export default function App() {
|
||||||
<Loading />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
<Routes>
|
<Routes>
|
||||||
{/*
|
|
||||||
Using this ugly code because the developers of react-router-dom decided that
|
|
||||||
removing regex support was a good idea.
|
|
||||||
https://github.com/remix-run/react-router/issues/7285
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route path="en">
|
|
||||||
<Route index element={<Home />} />
|
<Route index element={<Home />} />
|
||||||
<Route path="search" element={<Search />} />
|
<Route path="search" element={<Search />} />
|
||||||
<Route path="portfolio" element={<Portfolio />} />
|
<Route path="portfolio" element={<Portfolio />} />
|
||||||
<Route path="404" element={<NotFound />} />
|
<Route path="404" element={<NotFound />} />
|
||||||
<Route path="loading" element={<Loading />} />
|
<Route path="loading" element={<Loading />} />
|
||||||
<Route path="*" element={<Page />} />
|
<Route path="*" element={<Page />} />
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="kr">
|
|
||||||
<Route index element={<Home />} />
|
|
||||||
<Route path="search" element={<Search />} />
|
|
||||||
<Route path="portfolio" element={<Portfolio />} />
|
|
||||||
<Route path="404" element={<NotFound />} />
|
|
||||||
<Route path="loading" element={<Loading />} />
|
|
||||||
<Route path="*" element={<Page />} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
</Routes>
|
||||||
)}
|
)}
|
||||||
</StyledContentContainer>
|
</StyledContentContainer>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
import LocaleToggleButton from "./LocaleToggleButton"
|
|
||||||
import ThemeToggleButton from "./ThemeToggleButton"
|
import ThemeToggleButton from "./ThemeToggleButton"
|
||||||
import SearchButton from "./SearchButton"
|
import SearchButton from "./SearchButton"
|
||||||
|
|
||||||
|
@ -13,7 +12,6 @@ const RightButtons = styled.div`
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<RightButtons>
|
<RightButtons>
|
||||||
<LocaleToggleButton />
|
|
||||||
<ThemeToggleButton />
|
<ThemeToggleButton />
|
||||||
<SearchButton />
|
<SearchButton />
|
||||||
</RightButtons>
|
</RightButtons>
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
import type { SiteLocale } from "../../../globalContext"
|
|
||||||
|
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
|
||||||
import ReactTooltip from "react-tooltip"
|
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
|
||||||
import { faLanguage } from "@fortawesome/free-solid-svg-icons"
|
|
||||||
|
|
||||||
import { ActionsEnum, globalContext } from "../../../globalContext"
|
|
||||||
import { HeaderButtonCSS } from "../HeaderButton"
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
locale: SiteLocale
|
|
||||||
}
|
|
||||||
|
|
||||||
const LocaleToggleButton = styled.button<Props>`
|
|
||||||
${HeaderButtonCSS}
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
width: 72px;
|
|
||||||
|
|
||||||
${(props) => (props.locale == "en" ? "" : "transform: scaleX(-1);")};
|
|
||||||
`
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { globalState, dispatch } = useContext(globalContext)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LocaleToggleButton
|
|
||||||
data-tip
|
|
||||||
data-for="locale"
|
|
||||||
onClick={() => {
|
|
||||||
dispatch({
|
|
||||||
type: ActionsEnum.UPDATE_LOCALE,
|
|
||||||
payload: globalState.locale == "en" ? "kr" : "en",
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
locale={globalState.locale}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faLanguage} />
|
|
||||||
</LocaleToggleButton>
|
|
||||||
<ReactTooltip id="locale" type="dark" effect="solid">
|
|
||||||
<span>{globalState.locale == "en" ? "English" : "한국어"} </span>
|
|
||||||
</ReactTooltip>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import ReactTooltip from "react-tooltip"
|
import ReactTooltip from "react-tooltip"
|
||||||
|
|
||||||
|
@ -6,23 +5,18 @@ import HeaderButton from "../HeaderButton"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
import { globalContext } from "../../../globalContext"
|
|
||||||
|
|
||||||
const SearchButton = () => {
|
const SearchButton = () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Link data-tip data-for="search" to={`/${locale}/search`}>
|
<Link data-tip data-for="search" to="/search">
|
||||||
<HeaderButton>
|
<HeaderButton>
|
||||||
<FontAwesomeIcon icon={faSearch} />
|
<FontAwesomeIcon icon={faSearch} />
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ReactTooltip id="search" type="dark" effect="solid">
|
<ReactTooltip id="search" type="dark" effect="solid">
|
||||||
<span>{locale == "en" ? "Search" : "검색"}</span>
|
<span>Search</span>
|
||||||
</ReactTooltip>
|
</ReactTooltip>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,11 +40,7 @@ const ThemeToggleButton = () => {
|
||||||
|
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<ReactTooltip id="theme" type="dark" effect="solid">
|
<ReactTooltip id="theme" type="dark" effect="solid">
|
||||||
{globalState.locale == "en" ? (
|
|
||||||
<span>Using {theme} theme</span>
|
<span>Using {theme} theme</span>
|
||||||
) : (
|
|
||||||
<span>{theme == "dark" ? "어두운" : "밝은"} 테마 사용중</span>
|
|
||||||
)}
|
|
||||||
</ReactTooltip>
|
</ReactTooltip>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import ReadProgress from "./ReadProgress"
|
import ReadProgress from "./ReadProgress"
|
||||||
|
@ -7,7 +6,6 @@ import Nav from "./Nav"
|
||||||
|
|
||||||
import Sidebar from "../Sidebar"
|
import Sidebar from "../Sidebar"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
import Buttons from "./Buttons"
|
import Buttons from "./Buttons"
|
||||||
|
|
||||||
const Header = styled.header`
|
const Header = styled.header`
|
||||||
|
@ -44,13 +42,10 @@ const Icon = styled.img`
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header>
|
<Header>
|
||||||
<Container>
|
<Container>
|
||||||
<Link to={`/${locale}`}>
|
<Link to="/">
|
||||||
<Icon src="/icon/icon_circle.svg" />
|
<Icon src="/icon/icon_circle.svg" />
|
||||||
</Link>
|
</Link>
|
||||||
<Nav />
|
<Nav />
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
import HeaderButton from "./HeaderButton"
|
import HeaderButton from "./HeaderButton"
|
||||||
|
|
||||||
import NavbarData from "../../data/NavbarData"
|
import NavbarData from "../../data/NavbarData"
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
const Nav = styled.div`
|
const Nav = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -18,15 +16,11 @@ const Nav = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Nav>
|
<Nav>
|
||||||
{NavbarData.map((item, index) => (
|
{NavbarData.map((item, index) => (
|
||||||
<Link key={index} to={globalState.locale + item.path}>
|
<Link key={index} to={item.path}>
|
||||||
<HeaderButton>
|
<HeaderButton>{item.title}</HeaderButton>
|
||||||
{globalState.locale == "en" ? item.title_en : item.title_kr}
|
|
||||||
</HeaderButton>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</Nav>
|
</Nav>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
|
@ -15,8 +14,6 @@ import Tag from "./Tag"
|
||||||
import TagList from "./TagList"
|
import TagList from "./TagList"
|
||||||
import MainContent from "./MainContent"
|
import MainContent from "./MainContent"
|
||||||
|
|
||||||
import { globalContext } from "../globalContext"
|
|
||||||
|
|
||||||
const PostCard = styled(MainContent)`
|
const PostCard = styled(MainContent)`
|
||||||
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -63,13 +60,9 @@ export default (props: Props) => {
|
||||||
const { postData } = props
|
const { postData } = props
|
||||||
const { content_id, wordCount, date, readTime, title, tags } = postData
|
const { content_id, wordCount, date, readTime, title, tags } = postData
|
||||||
|
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostCard>
|
<PostCard>
|
||||||
<PostCardContainer
|
<PostCardContainer to={content_id}>
|
||||||
to={`/${globalState.locale}${content_id.replace(/(.kr)$/g, "")}`}
|
|
||||||
>
|
|
||||||
<Title>
|
<Title>
|
||||||
{title || "No title"}
|
{title || "No title"}
|
||||||
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
|
|
||||||
import type { Item } from "../../data/NavbarData"
|
import type { Item } from "../../data/NavbarData"
|
||||||
|
|
||||||
import { useCallback, useContext, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
import button from "../../styles/button"
|
import button from "../../styles/button"
|
||||||
|
|
||||||
const SidebarLink = styled(Link)`
|
const SidebarLink = styled(Link)`
|
||||||
|
@ -41,7 +40,6 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubMenu = (props: Props) => {
|
const SubMenu = (props: Props) => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const [isSubNavOpen, setSubNavOpen] = useState(false)
|
const [isSubNavOpen, setSubNavOpen] = useState(false)
|
||||||
const handleSidebarLinkClick = useCallback(() => {
|
const handleSidebarLinkClick = useCallback(() => {
|
||||||
setSubNavOpen((prev) => !prev)
|
setSubNavOpen((prev) => !prev)
|
||||||
|
@ -49,17 +47,10 @@ const SubMenu = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SidebarLink
|
<SidebarLink to={props.item.path} onClick={handleSidebarLinkClick}>
|
||||||
to={globalState.locale + props.item.path}
|
|
||||||
onClick={handleSidebarLinkClick}
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
{props.item.icon}
|
{props.item.icon}
|
||||||
<SidebarLabel>
|
<SidebarLabel>{props.item.title}</SidebarLabel>
|
||||||
{globalState.locale == "en"
|
|
||||||
? props.item.title_en
|
|
||||||
: props.item.title_kr}
|
|
||||||
</SidebarLabel>
|
|
||||||
</div>
|
</div>
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -10,32 +10,27 @@ import {
|
||||||
export type Item = {
|
export type Item = {
|
||||||
path: string
|
path: string
|
||||||
icon: JSX.Element
|
icon: JSX.Element
|
||||||
title_en: string
|
title: string
|
||||||
title_kr: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavbarData: Item[] = [
|
const NavbarData: Item[] = [
|
||||||
{
|
{
|
||||||
title_en: "Home",
|
title: "Home",
|
||||||
title_kr: "홈",
|
|
||||||
path: "/",
|
path: "/",
|
||||||
icon: <FontAwesomeIcon icon={faHome} />,
|
icon: <FontAwesomeIcon icon={faHome} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title_en: "About",
|
title: "About",
|
||||||
title_kr: "소개",
|
|
||||||
path: "/about",
|
path: "/about",
|
||||||
icon: <FontAwesomeIcon icon={faUser} />,
|
icon: <FontAwesomeIcon icon={faUser} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title_en: "Portfolio",
|
title: "Portfolio",
|
||||||
title_kr: "포트폴리오",
|
|
||||||
path: "/portfolio",
|
path: "/portfolio",
|
||||||
icon: <FontAwesomeIcon icon={faFileLines} />,
|
icon: <FontAwesomeIcon icon={faFileLines} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title_en: "Resume",
|
title: "Resume",
|
||||||
title_kr: "이력서",
|
|
||||||
path: "/resume",
|
path: "/resume",
|
||||||
icon: <FontAwesomeIcon icon={faUserTie} />,
|
icon: <FontAwesomeIcon icon={faUserTie} />,
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,6 @@ import lightTheme from "@developomp-site/theme/dist/light.json"
|
||||||
import { createContext, useEffect, useReducer } from "react"
|
import { createContext, useEffect, useReducer } from "react"
|
||||||
import storage from "local-storage-fallback"
|
import storage from "local-storage-fallback"
|
||||||
|
|
||||||
export type SiteLocale = "en" | "kr"
|
|
||||||
export type SiteTheme = "dark" | "light"
|
export type SiteTheme = "dark" | "light"
|
||||||
|
|
||||||
export enum ActionsEnum {
|
export enum ActionsEnum {
|
||||||
|
@ -14,18 +13,13 @@ export enum ActionsEnum {
|
||||||
UPDATE_LOCALE,
|
UPDATE_LOCALE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GlobalAction =
|
// union of all actions
|
||||||
| {
|
export type GlobalAction = {
|
||||||
type: ActionsEnum.UPDATE_THEME
|
type: ActionsEnum.UPDATE_THEME
|
||||||
payload: SiteTheme
|
payload: SiteTheme
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: ActionsEnum.UPDATE_LOCALE
|
|
||||||
payload: SiteLocale
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGlobalState {
|
export interface IGlobalState {
|
||||||
locale: SiteLocale
|
|
||||||
currentTheme: SiteTheme
|
currentTheme: SiteTheme
|
||||||
theme: Theme
|
theme: Theme
|
||||||
}
|
}
|
||||||
|
@ -35,15 +29,7 @@ export interface IGlobalContext {
|
||||||
dispatch: Dispatch<GlobalAction>
|
dispatch: Dispatch<GlobalAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultLocale(): SiteLocale {
|
|
||||||
if (window.location.pathname.startsWith("/en")) return "en"
|
|
||||||
if (window.location.pathname.startsWith("/kr")) return "kr"
|
|
||||||
|
|
||||||
return (storage.getItem("locale") as SiteLocale) || "en"
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultState: IGlobalState = {
|
const defaultState: IGlobalState = {
|
||||||
locale: getDefaultLocale(),
|
|
||||||
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
|
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
|
||||||
theme:
|
theme:
|
||||||
((storage.getItem("theme") || "dark") as SiteTheme) === "dark"
|
((storage.getItem("theme") || "dark") as SiteTheme) === "dark"
|
||||||
|
@ -60,10 +46,6 @@ function reducer(state = defaultState, action: GlobalAction): IGlobalState {
|
||||||
state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme
|
state.theme = state.currentTheme === "dark" ? darkTheme : lightTheme
|
||||||
break
|
break
|
||||||
|
|
||||||
case ActionsEnum.UPDATE_LOCALE:
|
|
||||||
state.locale = action.payload
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -79,11 +61,6 @@ export function GlobalStore(props: { children: ReactNode }): ReactElement {
|
||||||
storage.setItem("theme", globalState.currentTheme)
|
storage.setItem("theme", globalState.currentTheme)
|
||||||
}, [globalState.currentTheme])
|
}, [globalState.currentTheme])
|
||||||
|
|
||||||
// save locale when it is changed
|
|
||||||
useEffect(() => {
|
|
||||||
storage.setItem("locale", globalState.locale)
|
|
||||||
}, [globalState.locale])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<globalContext.Provider value={{ globalState, dispatch }}>
|
<globalContext.Provider value={{ globalState, dispatch }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
import type { Map } from "../../../types/types"
|
import type { Map } from "../../../types/types"
|
||||||
|
|
||||||
import { useCallback, useContext, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
import { Helmet } from "react-helmet-async"
|
import { Helmet } from "react-helmet-async"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
|
@ -13,8 +13,6 @@ import ShowMoreButton from "./ShowMoreButton"
|
||||||
|
|
||||||
import _map from "../../data/map.json"
|
import _map from "../../data/map.json"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
const map: Map = _map
|
const map: Map = _map
|
||||||
|
|
||||||
const PostList = styled.div`
|
const PostList = styled.div`
|
||||||
|
@ -26,9 +24,6 @@ const PostList = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
const [howMany, setHowMany] = useState(5)
|
const [howMany, setHowMany] = useState(5)
|
||||||
const [postsLength, setPostsLength] = useState(0)
|
const [postsLength, setPostsLength] = useState(0)
|
||||||
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
||||||
|
@ -71,14 +66,14 @@ export default () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | {locale == "en" ? "Home" : "홈"}</title>
|
<title>pomp | Home</title>
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:image" content="/icon/icon.svg" />
|
<meta property="og:image" content="/icon/icon.svg" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<PostList>
|
<PostList>
|
||||||
<h1>{locale == "en" ? "Recent Posts" : "최근 포스트"}</h1>
|
<h1>Recent Posts</h1>
|
||||||
|
|
||||||
{postCards}
|
{postCards}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
import buttonStyle from "../../styles/button"
|
import buttonStyle from "../../styles/button"
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
|
@ -16,11 +14,5 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: Props) => {
|
export default (props: Props) => {
|
||||||
const { globalState } = useContext(globalContext)
|
return <Button onClick={props.action}>Show more posts</Button>
|
||||||
|
|
||||||
return (
|
|
||||||
<Button onClick={props.action}>
|
|
||||||
{globalState.locale == "en" ? "Show more posts" : "더 많은 포스트 보이기"}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { Helmet } from "react-helmet-async"
|
import { Helmet } from "react-helmet-async"
|
||||||
|
|
||||||
import MainContent from "../components/MainContent"
|
import MainContent from "../components/MainContent"
|
||||||
|
|
||||||
import { globalContext } from "../globalContext"
|
|
||||||
|
|
||||||
const StyledNotFound = styled(MainContent)`
|
const StyledNotFound = styled(MainContent)`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
`
|
`
|
||||||
|
@ -15,8 +12,6 @@ const Styled404 = styled.h1`
|
||||||
`
|
`
|
||||||
|
|
||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
@ -35,9 +30,7 @@ const NotFound = () => {
|
||||||
<StyledNotFound>
|
<StyledNotFound>
|
||||||
<Styled404>404</Styled404>
|
<Styled404>404</Styled404>
|
||||||
<br />
|
<br />
|
||||||
{globalState.locale == "en"
|
Page was not found :(
|
||||||
? "Page was not found :("
|
|
||||||
: "페이지를 찾을 수 없습니다 :("}
|
|
||||||
</StyledNotFound>
|
</StyledNotFound>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { Helmet } from "react-helmet-async"
|
import { Helmet } from "react-helmet-async"
|
||||||
import { useLocation } from "react-router-dom"
|
import { useLocation } from "react-router-dom"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
@ -12,20 +12,16 @@ import Badge from "../../components/Badge"
|
||||||
import Tag from "../../components/Tag"
|
import Tag from "../../components/Tag"
|
||||||
import NotFound from "../NotFound"
|
import NotFound from "../NotFound"
|
||||||
|
|
||||||
import TranslationNotAvailable from "./TranslationNotAvailable"
|
|
||||||
import SeriesControlButtons from "./SeriesControlButtons"
|
import SeriesControlButtons from "./SeriesControlButtons"
|
||||||
import {
|
import {
|
||||||
categorizePageType,
|
categorizePageType,
|
||||||
checkURLValidity,
|
|
||||||
fetchContent,
|
fetchContent,
|
||||||
PageType,
|
PageType,
|
||||||
URLValidity,
|
|
||||||
parsePageData,
|
parsePageData,
|
||||||
} from "./helper"
|
} from "./helper"
|
||||||
import Meta from "./Meta"
|
import Meta from "./Meta"
|
||||||
import Toc from "./Toc"
|
import Toc from "./Toc"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
import type { PageData, Map } from "../../../types/types"
|
import type { PageData, Map } from "../../../types/types"
|
||||||
|
|
||||||
import _map from "../../data/map.json"
|
import _map from "../../data/map.json"
|
||||||
|
@ -54,53 +50,18 @@ const ProjectImage = styled.img`
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
|
|
||||||
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
||||||
const [pageType, setPageType] = useState<PageType>(PageType.POST)
|
const [pageType, setPageType] = useState<PageType>(PageType.POST)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isTranslationAvailable, setIsTranslationAvailable] = useState(true)
|
|
||||||
|
|
||||||
// this code runs if either the url or the locale changes
|
// this code runs if either the url or the locale changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const content_id =
|
const content_id = pathname.replace(/\/$/, "") // remove trailing slash
|
||||||
pathname
|
|
||||||
.replace(/^\/kr/, "") // remove /kr prefix
|
|
||||||
.replace(/^\/en/, "") // remove /en prefix
|
|
||||||
.replace(/\/$/, "") + // remove trailing slash
|
|
||||||
(locale == "en" ? "" : ".kr")
|
|
||||||
|
|
||||||
const pageType = categorizePageType(content_id)
|
const pageType = categorizePageType(content_id)
|
||||||
|
|
||||||
switch (checkURLValidity(content_id, pageType)) {
|
fetchContent(pageType, content_id).then((fetched_content) => {
|
||||||
case URLValidity.VALID: {
|
|
||||||
// continue if the URL is valid
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case URLValidity.VALID_BUT_IN_OTHER_LOCALE: {
|
|
||||||
// stop loading and set isTranslationAvailable to true so translation not available page will display
|
|
||||||
setIsTranslationAvailable(false)
|
|
||||||
setIsLoading(false)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
case URLValidity.NOT_VALID: {
|
|
||||||
// stop loading without fetching pageData so 404 page will display
|
|
||||||
setIsLoading(false)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get page data
|
|
||||||
*/
|
|
||||||
|
|
||||||
fetchContent(pageType, content_id, locale).then((fetched_content) => {
|
|
||||||
if (!fetched_content) {
|
if (!fetched_content) {
|
||||||
// stop loading without fetching pageData so 404 page will display
|
// stop loading without fetching pageData so 404 page will display
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
@ -108,17 +69,14 @@ export default function Page() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setPageData(parsePageData(fetched_content, pageType, content_id, locale))
|
setPageData(parsePageData(fetched_content, pageType, content_id))
|
||||||
setIsTranslationAvailable(true)
|
|
||||||
setPageType(pageType)
|
setPageType(pageType)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
}, [pathname, locale])
|
}, [pathname])
|
||||||
|
|
||||||
if (isLoading) return <Loading />
|
if (isLoading) return <Loading />
|
||||||
|
|
||||||
if (!isTranslationAvailable) return <TranslationNotAvailable />
|
|
||||||
|
|
||||||
if (!pageData) return <NotFound />
|
if (!pageData) return <NotFound />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
|
@ -10,7 +9,6 @@ import {
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
|
||||||
import buttonStyle from "../../styles/button"
|
import buttonStyle from "../../styles/button"
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -35,13 +33,10 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{prevURL ? (
|
{prevURL ? (
|
||||||
<Link to={`/${locale}${prevURL}`}>
|
<Link to={prevURL}>
|
||||||
<Button>
|
<Button>
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -52,14 +47,14 @@ function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
||||||
</DisabledButton>
|
</DisabledButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Link to={`/${locale}${seriesHome}`}>
|
<Link to={seriesHome}>
|
||||||
<Button>
|
<Button>
|
||||||
<FontAwesomeIcon icon={faListUl} />
|
<FontAwesomeIcon icon={faListUl} />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{nextURL ? (
|
{nextURL ? (
|
||||||
<Link to={`/${locale}${nextURL}`}>
|
<Link to={nextURL}>
|
||||||
<Button>
|
<Button>
|
||||||
<FontAwesomeIcon icon={faArrowRight} />
|
<FontAwesomeIcon icon={faArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Collapse } from "react-collapse"
|
import { Collapse } from "react-collapse"
|
||||||
import storage from "local-storage-fallback"
|
import storage from "local-storage-fallback"
|
||||||
|
|
||||||
|
@ -6,8 +6,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
|
import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
const StyledTocToggleButton = styled.button`
|
const StyledTocToggleButton = styled.button`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -25,7 +23,6 @@ const StyledCollapseContainer = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const Toc = (props: { data?: string }) => {
|
const Toc = (props: { data?: string }) => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const [isTocOpened, setIsTocOpened] = useState(
|
const [isTocOpened, setIsTocOpened] = useState(
|
||||||
storage.getItem("isTocOpened") == "true"
|
storage.getItem("isTocOpened") == "true"
|
||||||
)
|
)
|
||||||
|
@ -44,7 +41,7 @@ const Toc = (props: { data?: string }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong>
|
<strong>
|
||||||
{globalState.locale == "en" ? "Table of Contents " : "목차 "}
|
Table of Contents
|
||||||
<FontAwesomeIcon icon={isTocOpened ? faCaretUp : faCaretDown} />
|
<FontAwesomeIcon icon={isTocOpened ? faCaretUp : faCaretDown} />
|
||||||
</strong>
|
</strong>
|
||||||
</StyledTocToggleButton>
|
</StyledTocToggleButton>
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { useContext } from "react"
|
|
||||||
import styled from "styled-components"
|
|
||||||
import { Helmet } from "react-helmet-async"
|
|
||||||
|
|
||||||
import MainContent from "../../components/MainContent"
|
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
const Card = styled(MainContent)`
|
|
||||||
text-align: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Title = styled.h1`
|
|
||||||
font-size: 3rem;
|
|
||||||
`
|
|
||||||
|
|
||||||
const TranslationNotAvailable = () => {
|
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
const localized_title =
|
|
||||||
locale == "en" ? "Translation not found" : "번역이 존재하지 않습니다"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>pomp | {localized_title}</title>
|
|
||||||
|
|
||||||
<meta property="og:title" content={localized_title} />
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="http://developomp.com/icon/icon.svg"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content={
|
|
||||||
locale == "en"
|
|
||||||
? "This content is not available in English."
|
|
||||||
: "본 내용의 한국어 번역이 존재하지 않습니다"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<Title>{localized_title}</Title>
|
|
||||||
<br />
|
|
||||||
{locale == "en" ? (
|
|
||||||
<>
|
|
||||||
This post is only available in <b>Korean (한국어)</b>.
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
본 내용은 <b>영어(English)</b> 로만 제공됩니다.
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TranslationNotAvailable
|
|
|
@ -1,8 +1,7 @@
|
||||||
import portfolio from "../../data/portfolio.json"
|
import portfolio from "../../data/portfolio.json"
|
||||||
import _map from "../../data/map.json"
|
import _map from "../../data/map.json"
|
||||||
|
|
||||||
import type { SiteLocale } from "../../globalContext"
|
import type { Map, PageData } from "../../../types/types"
|
||||||
import type { Map, PageData, PortfolioProject } from "../../../types/types"
|
|
||||||
|
|
||||||
const map: Map = _map
|
const map: Map = _map
|
||||||
|
|
||||||
|
@ -14,40 +13,12 @@ export enum PageType {
|
||||||
UNSEARCHABLE,
|
UNSEARCHABLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum URLValidity {
|
export async function fetchContent(pageType: PageType, url: string) {
|
||||||
VALID, // page does exist in selected language
|
|
||||||
VALID_BUT_IN_OTHER_LOCALE, // page exists but only in another language
|
|
||||||
NOT_VALID, // page does not exist
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchContent(
|
|
||||||
pageType: PageType,
|
|
||||||
url: string,
|
|
||||||
locale: SiteLocale
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
if (pageType == PageType.UNSEARCHABLE) {
|
if (pageType == PageType.UNSEARCHABLE) {
|
||||||
if (locale == "en") {
|
|
||||||
return await import(`../../data/content/unsearchable${url}.json`)
|
return await import(`../../data/content/unsearchable${url}.json`)
|
||||||
} else {
|
} else {
|
||||||
try {
|
|
||||||
return await import(
|
|
||||||
`../../data/content/unsearchable${url}.${locale}.json`
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
return await import(`../../data/content/unsearchable${url}.json`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale == "en") {
|
|
||||||
return await import(`../../data/content${url}.json`)
|
return await import(`../../data/content${url}.json`)
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return await import(`../../data/content${url}.${locale}.json`)
|
|
||||||
} catch {
|
|
||||||
return await import(`../../data/content${url}.json`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return
|
return
|
||||||
|
@ -69,93 +40,11 @@ export function categorizePageType(content_id: string): PageType {
|
||||||
return PageType.UNSEARCHABLE
|
return PageType.UNSEARCHABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkURLValidity(
|
|
||||||
content_id: string,
|
|
||||||
pageType: PageType
|
|
||||||
): URLValidity {
|
|
||||||
// content ID of other language
|
|
||||||
const alt_content_id = content_id.endsWith(".kr")
|
|
||||||
? content_id.replace(/\.kr$/, "") // remove .kr suffix
|
|
||||||
: content_id + ".kr" // add .kr suffix
|
|
||||||
|
|
||||||
switch (pageType) {
|
|
||||||
case PageType.SERIES:
|
|
||||||
case PageType.POST: {
|
|
||||||
if (map.posts[content_id]) return URLValidity.VALID
|
|
||||||
|
|
||||||
if (map.posts[alt_content_id])
|
|
||||||
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case PageType.SERIES_HOME: {
|
|
||||||
const series_keys = Object.keys(map.series)
|
|
||||||
|
|
||||||
if (
|
|
||||||
series_keys.some((seriesHomeURL) =>
|
|
||||||
seriesHomeURL.startsWith(content_id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return URLValidity.VALID
|
|
||||||
|
|
||||||
if (
|
|
||||||
series_keys.some((seriesHomeURL) =>
|
|
||||||
seriesHomeURL.startsWith(alt_content_id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case PageType.PORTFOLIO_PROJECT: {
|
|
||||||
const locale: SiteLocale = content_id.endsWith(".kr") ? "kr" : "en"
|
|
||||||
const portfolio_content_id = content_id.endsWith(".kr")
|
|
||||||
? content_id.slice(0, content_id.length - 3)
|
|
||||||
: content_id
|
|
||||||
|
|
||||||
try {
|
|
||||||
const project = portfolio.projects[
|
|
||||||
portfolio_content_id as keyof typeof portfolio.projects
|
|
||||||
] as PortfolioProject
|
|
||||||
|
|
||||||
if (locale == "en" && project.overview_en) {
|
|
||||||
return URLValidity.VALID
|
|
||||||
} else if (locale == "kr" && project.overview_kr) {
|
|
||||||
return URLValidity.VALID
|
|
||||||
} else if (
|
|
||||||
(locale == "en" && project.overview_kr) ||
|
|
||||||
(locale == "kr" && project.overview_en)
|
|
||||||
) {
|
|
||||||
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// prevent linting error
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case PageType.UNSEARCHABLE: {
|
|
||||||
if (map.unsearchable[content_id]) return URLValidity.VALID
|
|
||||||
|
|
||||||
if (map.unsearchable[alt_content_id])
|
|
||||||
return URLValidity.VALID_BUT_IN_OTHER_LOCALE
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return URLValidity.NOT_VALID
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parsePageData(
|
export function parsePageData(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
fetched_content: any,
|
fetched_content: any,
|
||||||
pageType: PageType,
|
pageType: PageType,
|
||||||
content_id: string,
|
content_id: string
|
||||||
locale: SiteLocale
|
|
||||||
): PageData {
|
): PageData {
|
||||||
// page date to be saved as a react state
|
// page date to be saved as a react state
|
||||||
const pageData: PageData = {
|
const pageData: PageData = {
|
||||||
|
@ -247,21 +136,15 @@ export function parsePageData(
|
||||||
}
|
}
|
||||||
|
|
||||||
case PageType.PORTFOLIO_PROJECT: {
|
case PageType.PORTFOLIO_PROJECT: {
|
||||||
const portfolio_content_id = content_id.endsWith(".kr")
|
|
||||||
? content_id.slice(0, content_id.length - 3)
|
|
||||||
: content_id
|
|
||||||
|
|
||||||
const data =
|
const data =
|
||||||
portfolio.projects[
|
portfolio.projects[content_id as keyof typeof portfolio.projects]
|
||||||
portfolio_content_id as keyof typeof portfolio.projects
|
|
||||||
]
|
|
||||||
|
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
pageData.toc = fetched_content.toc
|
pageData.toc = fetched_content.toc
|
||||||
|
|
||||||
pageData.title = data.name
|
pageData.title = data.name
|
||||||
pageData.image = data.image
|
pageData.image = data.image
|
||||||
pageData.overview = locale == "en" ? data.overview_en : data.overview_kr
|
pageData.overview = data.overview
|
||||||
pageData.badges = data.badges
|
pageData.badges = data.badges
|
||||||
pageData.repo = data.repo
|
pageData.repo = data.repo
|
||||||
|
|
||||||
|
@ -269,10 +152,7 @@ export function parsePageData(
|
||||||
}
|
}
|
||||||
|
|
||||||
case PageType.UNSEARCHABLE: {
|
case PageType.UNSEARCHABLE: {
|
||||||
pageData.title = (
|
pageData.title = map.unsearchable[content_id].title
|
||||||
map.unsearchable[`${content_id}.${locale}`] ||
|
|
||||||
map.unsearchable[content_id]
|
|
||||||
).title
|
|
||||||
pageData.content = fetched_content.content
|
pageData.content = fetched_content.content
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Helmet } from "react-helmet-async"
|
import { Helmet } from "react-helmet-async"
|
||||||
|
|
||||||
import MainContent from "../../components/MainContent"
|
import MainContent from "../../components/MainContent"
|
||||||
|
@ -7,14 +7,9 @@ import ProjectCard from "./ProjectCard"
|
||||||
|
|
||||||
import portfolio from "../../data/portfolio.json"
|
import portfolio from "../../data/portfolio.json"
|
||||||
|
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
import type { PortfolioProject } from "../../../types/types"
|
import type { PortfolioProject } from "../../../types/types"
|
||||||
|
|
||||||
const Portfolio = () => {
|
const Portfolio = () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const locale = globalState.locale
|
|
||||||
|
|
||||||
const [projects, setProjects] = useState<JSX.Element[]>([])
|
const [projects, setProjects] = useState<JSX.Element[]>([])
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [skills, setSkills] = useState<JSX.Element[]>([])
|
const [skills, setSkills] = useState<JSX.Element[]>([])
|
||||||
|
@ -48,7 +43,7 @@ const Portfolio = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | {locale == "en" ? "Portfolio" : "포트폴리오"}</title>
|
<title>pomp | Portfolio</title>
|
||||||
|
|
||||||
<meta property="og:title" content="Portfolio" />
|
<meta property="og:title" content="Portfolio" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
@ -61,7 +56,7 @@ const Portfolio = () => {
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<MainContent>
|
<MainContent>
|
||||||
<h1>{locale == "en" ? "Portfolio" : "포트폴리오"}</h1>
|
<h1>Portfolio</h1>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
@ -70,8 +65,8 @@ const Portfolio = () => {
|
||||||
<h2 id="projects">
|
<h2 id="projects">
|
||||||
<a className="header-anchor" href="#projects">
|
<a className="header-anchor" href="#projects">
|
||||||
#
|
#
|
||||||
</a>{" "}
|
</a>
|
||||||
{locale == "en" ? "Projects" : "프로젝트"}
|
{" Projects"}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* todo: filter projects by skill */}
|
{/* todo: filter projects by skill */}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import Badge from "../../components/Badge"
|
||||||
import { cardCSS } from "../../components/Card"
|
import { cardCSS } from "../../components/Card"
|
||||||
|
|
||||||
import { PortfolioProject } from "../../../types/types"
|
import { PortfolioProject } from "../../../types/types"
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
const StyledProjectCard = styled.div`
|
const StyledProjectCard = styled.div`
|
||||||
${cardCSS}
|
${cardCSS}
|
||||||
|
@ -38,9 +37,6 @@ interface ProjectCardProps {
|
||||||
const ProjectCard = (props: ProjectCardProps) => {
|
const ProjectCard = (props: ProjectCardProps) => {
|
||||||
const { projectID, project } = props
|
const { projectID, project } = props
|
||||||
|
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const { locale } = globalState
|
|
||||||
|
|
||||||
const [badges, setBadges] = useState<JSX.Element[]>([])
|
const [badges, setBadges] = useState<JSX.Element[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -48,7 +44,7 @@ const ProjectCard = (props: ProjectCardProps) => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`/${locale}${projectID}`}>
|
<Link to={projectID}>
|
||||||
<StyledProjectCard>
|
<StyledProjectCard>
|
||||||
<h1>{project.name}</h1>
|
<h1>{project.name}</h1>
|
||||||
<StyledImg src={project.image} />
|
<StyledImg src={project.image} />
|
||||||
|
@ -57,7 +53,7 @@ const ProjectCard = (props: ProjectCardProps) => {
|
||||||
<hr />
|
<hr />
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: locale == "en" ? project.overview_en : project.overview_kr,
|
__html: project.overview,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</StyledProjectCard>
|
</StyledProjectCard>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useContext, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
import styled from "styled-components"
|
import styled from "styled-components"
|
||||||
import { useSearchParams } from "react-router-dom"
|
import { useSearchParams } from "react-router-dom"
|
||||||
import { Helmet } from "react-helmet-async"
|
import { Helmet } from "react-helmet-async"
|
||||||
|
@ -16,7 +16,6 @@ import MainContent from "../../components/MainContent"
|
||||||
import SearchBar from "./SearchBar"
|
import SearchBar from "./SearchBar"
|
||||||
import TagSelect, { TagsData } from "./TagSelect"
|
import TagSelect, { TagsData } from "./TagSelect"
|
||||||
import { ClearDateButton, DateRangeControl, StyledDateRange } from "./DateRange"
|
import { ClearDateButton, DateRangeControl, StyledDateRange } from "./DateRange"
|
||||||
import { globalContext } from "../../globalContext"
|
|
||||||
|
|
||||||
import "react-date-range/dist/styles.css"
|
import "react-date-range/dist/styles.css"
|
||||||
import "react-date-range/dist/theme/default.css"
|
import "react-date-range/dist/theme/default.css"
|
||||||
|
@ -98,9 +97,6 @@ function isSelectedTagsInPost(selectedTags?: TagsData[], postTags?: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Search = () => {
|
const Search = () => {
|
||||||
const { globalState } = useContext(globalContext)
|
|
||||||
const locale = globalState.locale
|
|
||||||
|
|
||||||
// URL search parameters
|
// URL search parameters
|
||||||
const [URLSearchParams, setURLSearchParams] = useSearchParams()
|
const [URLSearchParams, setURLSearchParams] = useSearchParams()
|
||||||
|
|
||||||
|
@ -222,11 +218,11 @@ const Search = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>pomp | {locale == "en" ? "Search" : "검색"}</title>
|
<title>pomp | Search</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<StyledSearch>
|
<StyledSearch>
|
||||||
<h1>{locale == "en" ? "Search" : "검색"}</h1>
|
<h1>Search</h1>
|
||||||
|
|
||||||
<StyledSearchContainer>
|
<StyledSearchContainer>
|
||||||
<DateRangeControl>
|
<DateRangeControl>
|
||||||
|
@ -235,7 +231,7 @@ const Search = () => {
|
||||||
setDateRange(defaultDateRange)
|
setDateRange(defaultDateRange)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{locale == "en" ? "Reset date range" : "날짜 범위 초기화"}
|
Reset date range
|
||||||
</ClearDateButton>
|
</ClearDateButton>
|
||||||
<StyledDateRange
|
<StyledDateRange
|
||||||
editableDateInputs
|
editableDateInputs
|
||||||
|
@ -256,15 +252,13 @@ const Search = () => {
|
||||||
type="search"
|
type="search"
|
||||||
value={searchInput}
|
value={searchInput}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={locale == "en" ? "Search" : "검색"}
|
placeholder="Search"
|
||||||
onChange={(event) => setSearchInput(event.target.value)}
|
onChange={(event) => setSearchInput(event.target.value)}
|
||||||
onKeyPress={(event) => {
|
onKeyPress={(event) => {
|
||||||
event.key === "Enter" && searchInput && doSearch()
|
event.key === "Enter" && searchInput && doSearch()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{locale == "kr" && "결과: "}
|
{postCards.length} result{postCards.length > 1 && "s"}
|
||||||
{postCards.length}{" "}
|
|
||||||
{locale == "en" && (postCards.length > 1 ? "results" : "result")}
|
|
||||||
<TagSelect
|
<TagSelect
|
||||||
defaultValue={selectedTags}
|
defaultValue={selectedTags}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
|
|
|
@ -32,13 +32,12 @@ interface TagSelectProps {
|
||||||
const TagSelect = (props: TagSelectProps) => {
|
const TagSelect = (props: TagSelectProps) => {
|
||||||
const { globalState } = useContext(globalContext)
|
const { globalState } = useContext(globalContext)
|
||||||
const { theme } = globalState
|
const { theme } = globalState
|
||||||
const locale = globalState.locale
|
|
||||||
const { onChange, defaultValue: selectedTags } = props
|
const { onChange, defaultValue: selectedTags } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledReactTagsContainer>
|
<StyledReactTagsContainer>
|
||||||
<Select
|
<Select
|
||||||
placeholder={locale == "en" ? "Select tags..." : "태그를 선택하세요"}
|
placeholder="Select tags..."
|
||||||
theme={(reactSelectTheme) => ({
|
theme={(reactSelectTheme) => ({
|
||||||
...reactSelectTheme,
|
...reactSelectTheme,
|
||||||
colors: {
|
colors: {
|
||||||
|
|
|
@ -128,8 +128,7 @@ export interface PortfolioOverview {
|
||||||
export interface PortfolioProject {
|
export interface PortfolioProject {
|
||||||
name: string
|
name: string
|
||||||
image: string // url to the image
|
image: string // url to the image
|
||||||
overview_en: string
|
overview: string
|
||||||
overview_kr: string
|
|
||||||
badges: string[] // array of valid simpleIcons slug
|
badges: string[] // array of valid simpleIcons slug
|
||||||
repo: string // url of the git repository
|
repo: string // url of the git repository
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue