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 {
|
||||
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) {
|
||||
;(markdownData.badges as string[]).forEach((slug) => {
|
||||
// todo: handle cases when icon is not on simple-icons
|
||||
|
@ -55,14 +36,10 @@ export default function parsePortfolio(data: DataToPass): void {
|
|||
portfolioData.projects[urlPath] = {
|
||||
name: markdownData.name as string,
|
||||
image: markdownData.image as string,
|
||||
overview_en: markdownData.overview as string,
|
||||
overview_kr: portfolioData.projects[urlPath]
|
||||
? portfolioData.projects[urlPath].overview_kr
|
||||
: "",
|
||||
overview: markdownData.overview as string,
|
||||
badges: (markdownData.badges as string[]) || [],
|
||||
repo: (markdownData.repo as string) || "",
|
||||
}
|
||||
}
|
||||
|
||||
writeToFile(
|
||||
`${contentDirectoryPath}${urlPath}.json`,
|
||||
|
|
|
@ -10,7 +10,6 @@ export default function parseUnsearchable(data: DataToPass): void {
|
|||
// convert path like /XXX/YYY/ZZZ to /YYY/ZZZ
|
||||
const urlPath = _urlPath.slice(_urlPath.slice(1).indexOf("/") + 1)
|
||||
|
||||
if (!urlPath.endsWith(".kr.md")) {
|
||||
addDocument({
|
||||
title: markdownData.title,
|
||||
body: markdownData.content,
|
||||
|
@ -21,7 +20,6 @@ export default function parseUnsearchable(data: DataToPass): void {
|
|||
map.unsearchable[urlPath] = {
|
||||
title: markdownData.title as string,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 { 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 { Helmet } from "react-helmet-async"
|
||||
import { isIE } from "react-device-detect"
|
||||
|
@ -37,18 +37,9 @@ const StyledContentContainer = styled.div`
|
|||
|
||||
export default function App() {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { pathname } = useLocation()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
// update url on locale change
|
||||
useEffect(() => {
|
||||
navigate(locale + pathname.slice(3))
|
||||
}, [locale])
|
||||
|
||||
useEffect(() => {
|
||||
// set loading to false if all fonts are loaded
|
||||
// checks if document.fonts.onloadingdone is supported on the browser
|
||||
|
@ -59,10 +50,6 @@ export default function App() {
|
|||
} else {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
// automatically add locale prefix if it's not specified
|
||||
if (!pathname.startsWith("/en") && !pathname.startsWith("/kr"))
|
||||
navigate(`/${globalState.locale}${pathname}`)
|
||||
}, [])
|
||||
|
||||
if (isIE)
|
||||
|
@ -93,29 +80,12 @@ export default function App() {
|
|||
<Loading />
|
||||
) : (
|
||||
<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 path="search" element={<Search />} />
|
||||
<Route path="portfolio" element={<Portfolio />} />
|
||||
<Route path="404" element={<NotFound />} />
|
||||
<Route path="loading" element={<Loading />} />
|
||||
<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>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import styled from "styled-components"
|
||||
|
||||
import LocaleToggleButton from "./LocaleToggleButton"
|
||||
import ThemeToggleButton from "./ThemeToggleButton"
|
||||
import SearchButton from "./SearchButton"
|
||||
|
||||
|
@ -13,7 +12,6 @@ const RightButtons = styled.div`
|
|||
export default () => {
|
||||
return (
|
||||
<RightButtons>
|
||||
<LocaleToggleButton />
|
||||
<ThemeToggleButton />
|
||||
<SearchButton />
|
||||
</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 ReactTooltip from "react-tooltip"
|
||||
|
||||
|
@ -6,23 +5,18 @@ import HeaderButton from "../HeaderButton"
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import { globalContext } from "../../../globalContext"
|
||||
|
||||
const SearchButton = () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Link data-tip data-for="search" to={`/${locale}/search`}>
|
||||
<Link data-tip data-for="search" to="/search">
|
||||
<HeaderButton>
|
||||
<FontAwesomeIcon icon={faSearch} />
|
||||
</HeaderButton>
|
||||
</Link>
|
||||
</div>
|
||||
<ReactTooltip id="search" type="dark" effect="solid">
|
||||
<span>{locale == "en" ? "Search" : "검색"}</span>
|
||||
<span>Search</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -40,11 +40,7 @@ const ThemeToggleButton = () => {
|
|||
|
||||
{!isMobile && (
|
||||
<ReactTooltip id="theme" type="dark" effect="solid">
|
||||
{globalState.locale == "en" ? (
|
||||
<span>Using {theme} theme</span>
|
||||
) : (
|
||||
<span>{theme == "dark" ? "어두운" : "밝은"} 테마 사용중</span>
|
||||
)}
|
||||
</ReactTooltip>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useContext } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
import ReadProgress from "./ReadProgress"
|
||||
|
@ -7,7 +6,6 @@ import Nav from "./Nav"
|
|||
|
||||
import Sidebar from "../Sidebar"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
import Buttons from "./Buttons"
|
||||
|
||||
const Header = styled.header`
|
||||
|
@ -44,13 +42,10 @@ const Icon = styled.img`
|
|||
`
|
||||
|
||||
export default () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<Container>
|
||||
<Link to={`/${locale}`}>
|
||||
<Link to="/">
|
||||
<Icon src="/icon/icon_circle.svg" />
|
||||
</Link>
|
||||
<Nav />
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import HeaderButton from "./HeaderButton"
|
||||
|
||||
import NavbarData from "../../data/NavbarData"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const Nav = styled.div`
|
||||
display: flex;
|
||||
|
@ -18,15 +16,11 @@ const Nav = styled.div`
|
|||
`
|
||||
|
||||
export default () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<Nav>
|
||||
{NavbarData.map((item, index) => (
|
||||
<Link key={index} to={globalState.locale + item.path}>
|
||||
<HeaderButton>
|
||||
{globalState.locale == "en" ? item.title_en : item.title_kr}
|
||||
</HeaderButton>
|
||||
<Link key={index} to={item.path}>
|
||||
<HeaderButton>{item.title}</HeaderButton>
|
||||
</Link>
|
||||
))}
|
||||
</Nav>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
|
@ -15,8 +14,6 @@ import Tag from "./Tag"
|
|||
import TagList from "./TagList"
|
||||
import MainContent from "./MainContent"
|
||||
|
||||
import { globalContext } from "../globalContext"
|
||||
|
||||
const PostCard = styled(MainContent)`
|
||||
box-shadow: 0 4px 10px rgb(0 0 0 / 10%);
|
||||
text-align: left;
|
||||
|
@ -63,13 +60,9 @@ export default (props: Props) => {
|
|||
const { postData } = props
|
||||
const { content_id, wordCount, date, readTime, title, tags } = postData
|
||||
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<PostCard>
|
||||
<PostCardContainer
|
||||
to={`/${globalState.locale}${content_id.replace(/(.kr)$/g, "")}`}
|
||||
>
|
||||
<PostCardContainer to={content_id}>
|
||||
<Title>
|
||||
{title || "No title"}
|
||||
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
|
||||
import type { Item } from "../../data/NavbarData"
|
||||
|
||||
import { useCallback, useContext, useState } from "react"
|
||||
import { useCallback, useState } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
import button from "../../styles/button"
|
||||
|
||||
const SidebarLink = styled(Link)`
|
||||
|
@ -41,7 +40,6 @@ interface Props {
|
|||
}
|
||||
|
||||
const SubMenu = (props: Props) => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const [isSubNavOpen, setSubNavOpen] = useState(false)
|
||||
const handleSidebarLinkClick = useCallback(() => {
|
||||
setSubNavOpen((prev) => !prev)
|
||||
|
@ -49,17 +47,10 @@ const SubMenu = (props: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<SidebarLink
|
||||
to={globalState.locale + props.item.path}
|
||||
onClick={handleSidebarLinkClick}
|
||||
>
|
||||
<SidebarLink to={props.item.path} onClick={handleSidebarLinkClick}>
|
||||
<div>
|
||||
{props.item.icon}
|
||||
<SidebarLabel>
|
||||
{globalState.locale == "en"
|
||||
? props.item.title_en
|
||||
: props.item.title_kr}
|
||||
</SidebarLabel>
|
||||
<SidebarLabel>{props.item.title}</SidebarLabel>
|
||||
</div>
|
||||
</SidebarLink>
|
||||
</>
|
||||
|
|
|
@ -10,32 +10,27 @@ import {
|
|||
export type Item = {
|
||||
path: string
|
||||
icon: JSX.Element
|
||||
title_en: string
|
||||
title_kr: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const NavbarData: Item[] = [
|
||||
{
|
||||
title_en: "Home",
|
||||
title_kr: "홈",
|
||||
title: "Home",
|
||||
path: "/",
|
||||
icon: <FontAwesomeIcon icon={faHome} />,
|
||||
},
|
||||
{
|
||||
title_en: "About",
|
||||
title_kr: "소개",
|
||||
title: "About",
|
||||
path: "/about",
|
||||
icon: <FontAwesomeIcon icon={faUser} />,
|
||||
},
|
||||
{
|
||||
title_en: "Portfolio",
|
||||
title_kr: "포트폴리오",
|
||||
title: "Portfolio",
|
||||
path: "/portfolio",
|
||||
icon: <FontAwesomeIcon icon={faFileLines} />,
|
||||
},
|
||||
{
|
||||
title_en: "Resume",
|
||||
title_kr: "이력서",
|
||||
title: "Resume",
|
||||
path: "/resume",
|
||||
icon: <FontAwesomeIcon icon={faUserTie} />,
|
||||
},
|
||||
|
|
|
@ -6,7 +6,6 @@ import lightTheme from "@developomp-site/theme/dist/light.json"
|
|||
import { createContext, useEffect, useReducer } from "react"
|
||||
import storage from "local-storage-fallback"
|
||||
|
||||
export type SiteLocale = "en" | "kr"
|
||||
export type SiteTheme = "dark" | "light"
|
||||
|
||||
export enum ActionsEnum {
|
||||
|
@ -14,18 +13,13 @@ export enum ActionsEnum {
|
|||
UPDATE_LOCALE,
|
||||
}
|
||||
|
||||
export type GlobalAction =
|
||||
| {
|
||||
// union of all actions
|
||||
export type GlobalAction = {
|
||||
type: ActionsEnum.UPDATE_THEME
|
||||
payload: SiteTheme
|
||||
}
|
||||
| {
|
||||
type: ActionsEnum.UPDATE_LOCALE
|
||||
payload: SiteLocale
|
||||
}
|
||||
|
||||
export interface IGlobalState {
|
||||
locale: SiteLocale
|
||||
currentTheme: SiteTheme
|
||||
theme: Theme
|
||||
}
|
||||
|
@ -35,15 +29,7 @@ export interface IGlobalContext {
|
|||
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 = {
|
||||
locale: getDefaultLocale(),
|
||||
currentTheme: (storage.getItem("theme") || "dark") as SiteTheme,
|
||||
theme:
|
||||
((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
|
||||
break
|
||||
|
||||
case ActionsEnum.UPDATE_LOCALE:
|
||||
state.locale = action.payload
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -79,11 +61,6 @@ export function GlobalStore(props: { children: ReactNode }): ReactElement {
|
|||
storage.setItem("theme", globalState.currentTheme)
|
||||
}, [globalState.currentTheme])
|
||||
|
||||
// save locale when it is changed
|
||||
useEffect(() => {
|
||||
storage.setItem("locale", globalState.locale)
|
||||
}, [globalState.locale])
|
||||
|
||||
return (
|
||||
<globalContext.Provider value={{ globalState, dispatch }}>
|
||||
{props.children}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
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 styled from "styled-components"
|
||||
|
||||
|
@ -13,8 +13,6 @@ import ShowMoreButton from "./ShowMoreButton"
|
|||
|
||||
import _map from "../../data/map.json"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const map: Map = _map
|
||||
|
||||
const PostList = styled.div`
|
||||
|
@ -26,9 +24,6 @@ const PostList = styled.div`
|
|||
`
|
||||
|
||||
export default () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
const [howMany, setHowMany] = useState(5)
|
||||
const [postsLength, setPostsLength] = useState(0)
|
||||
const [postCards, setPostCards] = useState<JSX.Element[]>([])
|
||||
|
@ -71,14 +66,14 @@ export default () => {
|
|||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {locale == "en" ? "Home" : "홈"}</title>
|
||||
<title>pomp | Home</title>
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content="/icon/icon.svg" />
|
||||
</Helmet>
|
||||
|
||||
<PostList>
|
||||
<h1>{locale == "en" ? "Recent Posts" : "최근 포스트"}</h1>
|
||||
<h1>Recent Posts</h1>
|
||||
|
||||
{postCards}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
import buttonStyle from "../../styles/button"
|
||||
|
||||
const Button = styled.button`
|
||||
|
@ -16,11 +14,5 @@ interface Props {
|
|||
}
|
||||
|
||||
export default (props: Props) => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<Button onClick={props.action}>
|
||||
{globalState.locale == "en" ? "Show more posts" : "더 많은 포스트 보이기"}
|
||||
</Button>
|
||||
)
|
||||
return <Button onClick={props.action}>Show more posts</Button>
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
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 StyledNotFound = styled(MainContent)`
|
||||
text-align: center;
|
||||
`
|
||||
|
@ -15,8 +12,6 @@ const Styled404 = styled.h1`
|
|||
`
|
||||
|
||||
const NotFound = () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
|
@ -35,9 +30,7 @@ const NotFound = () => {
|
|||
<StyledNotFound>
|
||||
<Styled404>404</Styled404>
|
||||
<br />
|
||||
{globalState.locale == "en"
|
||||
? "Page was not found :("
|
||||
: "페이지를 찾을 수 없습니다 :("}
|
||||
Page was not found :(
|
||||
</StyledNotFound>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext, useState, useEffect } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useLocation } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
|
@ -12,20 +12,16 @@ import Badge from "../../components/Badge"
|
|||
import Tag from "../../components/Tag"
|
||||
import NotFound from "../NotFound"
|
||||
|
||||
import TranslationNotAvailable from "./TranslationNotAvailable"
|
||||
import SeriesControlButtons from "./SeriesControlButtons"
|
||||
import {
|
||||
categorizePageType,
|
||||
checkURLValidity,
|
||||
fetchContent,
|
||||
PageType,
|
||||
URLValidity,
|
||||
parsePageData,
|
||||
} from "./helper"
|
||||
import Meta from "./Meta"
|
||||
import Toc from "./Toc"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
import type { PageData, Map } from "../../../types/types"
|
||||
|
||||
import _map from "../../data/map.json"
|
||||
|
@ -54,53 +50,18 @@ const ProjectImage = styled.img`
|
|||
`
|
||||
|
||||
export default function Page() {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
const { pathname } = useLocation()
|
||||
|
||||
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
||||
const [pageType, setPageType] = useState<PageType>(PageType.POST)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isTranslationAvailable, setIsTranslationAvailable] = useState(true)
|
||||
|
||||
// this code runs if either the url or the locale changes
|
||||
useEffect(() => {
|
||||
const content_id =
|
||||
pathname
|
||||
.replace(/^\/kr/, "") // remove /kr prefix
|
||||
.replace(/^\/en/, "") // remove /en prefix
|
||||
.replace(/\/$/, "") + // remove trailing slash
|
||||
(locale == "en" ? "" : ".kr")
|
||||
|
||||
const content_id = pathname.replace(/\/$/, "") // remove trailing slash
|
||||
const pageType = categorizePageType(content_id)
|
||||
|
||||
switch (checkURLValidity(content_id, pageType)) {
|
||||
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) => {
|
||||
fetchContent(pageType, content_id).then((fetched_content) => {
|
||||
if (!fetched_content) {
|
||||
// stop loading without fetching pageData so 404 page will display
|
||||
setIsLoading(false)
|
||||
|
@ -108,17 +69,14 @@ export default function Page() {
|
|||
return
|
||||
}
|
||||
|
||||
setPageData(parsePageData(fetched_content, pageType, content_id, locale))
|
||||
setIsTranslationAvailable(true)
|
||||
setPageData(parsePageData(fetched_content, pageType, content_id))
|
||||
setPageType(pageType)
|
||||
setIsLoading(false)
|
||||
})
|
||||
}, [pathname, locale])
|
||||
}, [pathname])
|
||||
|
||||
if (isLoading) return <Loading />
|
||||
|
||||
if (!isTranslationAvailable) return <TranslationNotAvailable />
|
||||
|
||||
if (!pageData) return <NotFound />
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useContext } from "react"
|
||||
import styled from "styled-components"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
|
@ -10,7 +9,6 @@ import {
|
|||
} from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
import buttonStyle from "../../styles/button"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
|
@ -35,13 +33,10 @@ interface Props {
|
|||
}
|
||||
|
||||
function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{prevURL ? (
|
||||
<Link to={`/${locale}${prevURL}`}>
|
||||
<Link to={prevURL}>
|
||||
<Button>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</Button>
|
||||
|
@ -52,14 +47,14 @@ function SeriesControlButtons({ prevURL, seriesHome, nextURL }: Props) {
|
|||
</DisabledButton>
|
||||
)}
|
||||
|
||||
<Link to={`/${locale}${seriesHome}`}>
|
||||
<Link to={seriesHome}>
|
||||
<Button>
|
||||
<FontAwesomeIcon icon={faListUl} />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{nextURL ? (
|
||||
<Link to={`/${locale}${nextURL}`}>
|
||||
<Link to={nextURL}>
|
||||
<Button>
|
||||
<FontAwesomeIcon icon={faArrowRight} />
|
||||
</Button>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext, useEffect, useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Collapse } from "react-collapse"
|
||||
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 styled from "styled-components"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const StyledTocToggleButton = styled.button`
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
|
@ -25,7 +23,6 @@ const StyledCollapseContainer = styled.div`
|
|||
`
|
||||
|
||||
const Toc = (props: { data?: string }) => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const [isTocOpened, setIsTocOpened] = useState(
|
||||
storage.getItem("isTocOpened") == "true"
|
||||
)
|
||||
|
@ -44,7 +41,7 @@ const Toc = (props: { data?: string }) => {
|
|||
}}
|
||||
>
|
||||
<strong>
|
||||
{globalState.locale == "en" ? "Table of Contents " : "목차 "}
|
||||
Table of Contents
|
||||
<FontAwesomeIcon icon={isTocOpened ? faCaretUp : faCaretDown} />
|
||||
</strong>
|
||||
</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 _map from "../../data/map.json"
|
||||
|
||||
import type { SiteLocale } from "../../globalContext"
|
||||
import type { Map, PageData, PortfolioProject } from "../../../types/types"
|
||||
import type { Map, PageData } from "../../../types/types"
|
||||
|
||||
const map: Map = _map
|
||||
|
||||
|
@ -14,40 +13,12 @@ export enum PageType {
|
|||
UNSEARCHABLE,
|
||||
}
|
||||
|
||||
export enum URLValidity {
|
||||
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
|
||||
) {
|
||||
export async function fetchContent(pageType: PageType, url: string) {
|
||||
try {
|
||||
if (pageType == PageType.UNSEARCHABLE) {
|
||||
if (locale == "en") {
|
||||
return await import(`../../data/content/unsearchable${url}.json`)
|
||||
} 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`)
|
||||
} else {
|
||||
try {
|
||||
return await import(`../../data/content${url}.${locale}.json`)
|
||||
} catch {
|
||||
return await import(`../../data/content${url}.json`)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return
|
||||
|
@ -69,93 +40,11 @@ export function categorizePageType(content_id: string): PageType {
|
|||
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(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fetched_content: any,
|
||||
pageType: PageType,
|
||||
content_id: string,
|
||||
locale: SiteLocale
|
||||
content_id: string
|
||||
): PageData {
|
||||
// page date to be saved as a react state
|
||||
const pageData: PageData = {
|
||||
|
@ -247,21 +136,15 @@ export function parsePageData(
|
|||
}
|
||||
|
||||
case PageType.PORTFOLIO_PROJECT: {
|
||||
const portfolio_content_id = content_id.endsWith(".kr")
|
||||
? content_id.slice(0, content_id.length - 3)
|
||||
: content_id
|
||||
|
||||
const data =
|
||||
portfolio.projects[
|
||||
portfolio_content_id as keyof typeof portfolio.projects
|
||||
]
|
||||
portfolio.projects[content_id as keyof typeof portfolio.projects]
|
||||
|
||||
pageData.content = fetched_content.content
|
||||
pageData.toc = fetched_content.toc
|
||||
|
||||
pageData.title = data.name
|
||||
pageData.image = data.image
|
||||
pageData.overview = locale == "en" ? data.overview_en : data.overview_kr
|
||||
pageData.overview = data.overview
|
||||
pageData.badges = data.badges
|
||||
pageData.repo = data.repo
|
||||
|
||||
|
@ -269,10 +152,7 @@ export function parsePageData(
|
|||
}
|
||||
|
||||
case PageType.UNSEARCHABLE: {
|
||||
pageData.title = (
|
||||
map.unsearchable[`${content_id}.${locale}`] ||
|
||||
map.unsearchable[content_id]
|
||||
).title
|
||||
pageData.title = map.unsearchable[content_id].title
|
||||
pageData.content = fetched_content.content
|
||||
|
||||
break
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext, useEffect, useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
||||
import MainContent from "../../components/MainContent"
|
||||
|
@ -7,14 +7,9 @@ import ProjectCard from "./ProjectCard"
|
|||
|
||||
import portfolio from "../../data/portfolio.json"
|
||||
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
import type { PortfolioProject } from "../../../types/types"
|
||||
|
||||
const Portfolio = () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const locale = globalState.locale
|
||||
|
||||
const [projects, setProjects] = useState<JSX.Element[]>([])
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [skills, setSkills] = useState<JSX.Element[]>([])
|
||||
|
@ -48,7 +43,7 @@ const Portfolio = () => {
|
|||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {locale == "en" ? "Portfolio" : "포트폴리오"}</title>
|
||||
<title>pomp | Portfolio</title>
|
||||
|
||||
<meta property="og:title" content="Portfolio" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
@ -61,7 +56,7 @@ const Portfolio = () => {
|
|||
</Helmet>
|
||||
|
||||
<MainContent>
|
||||
<h1>{locale == "en" ? "Portfolio" : "포트폴리오"}</h1>
|
||||
<h1>Portfolio</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
|
@ -70,8 +65,8 @@ const Portfolio = () => {
|
|||
<h2 id="projects">
|
||||
<a className="header-anchor" href="#projects">
|
||||
#
|
||||
</a>{" "}
|
||||
{locale == "en" ? "Projects" : "프로젝트"}
|
||||
</a>
|
||||
{" Projects"}
|
||||
</h2>
|
||||
|
||||
{/* 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 { Link } from "react-router-dom"
|
||||
|
||||
|
@ -6,7 +6,6 @@ import Badge from "../../components/Badge"
|
|||
import { cardCSS } from "../../components/Card"
|
||||
|
||||
import { PortfolioProject } from "../../../types/types"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
const StyledProjectCard = styled.div`
|
||||
${cardCSS}
|
||||
|
@ -38,9 +37,6 @@ interface ProjectCardProps {
|
|||
const ProjectCard = (props: ProjectCardProps) => {
|
||||
const { projectID, project } = props
|
||||
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { locale } = globalState
|
||||
|
||||
const [badges, setBadges] = useState<JSX.Element[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -48,7 +44,7 @@ const ProjectCard = (props: ProjectCardProps) => {
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<Link to={`/${locale}${projectID}`}>
|
||||
<Link to={projectID}>
|
||||
<StyledProjectCard>
|
||||
<h1>{project.name}</h1>
|
||||
<StyledImg src={project.image} />
|
||||
|
@ -57,7 +53,7 @@ const ProjectCard = (props: ProjectCardProps) => {
|
|||
<hr />
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: locale == "en" ? project.overview_en : project.overview_kr,
|
||||
__html: project.overview,
|
||||
}}
|
||||
/>
|
||||
</StyledProjectCard>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useContext, useEffect, useState } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
@ -16,7 +16,6 @@ import MainContent from "../../components/MainContent"
|
|||
import SearchBar from "./SearchBar"
|
||||
import TagSelect, { TagsData } from "./TagSelect"
|
||||
import { ClearDateButton, DateRangeControl, StyledDateRange } from "./DateRange"
|
||||
import { globalContext } from "../../globalContext"
|
||||
|
||||
import "react-date-range/dist/styles.css"
|
||||
import "react-date-range/dist/theme/default.css"
|
||||
|
@ -98,9 +97,6 @@ function isSelectedTagsInPost(selectedTags?: TagsData[], postTags?: string[]) {
|
|||
}
|
||||
|
||||
const Search = () => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const locale = globalState.locale
|
||||
|
||||
// URL search parameters
|
||||
const [URLSearchParams, setURLSearchParams] = useSearchParams()
|
||||
|
||||
|
@ -222,11 +218,11 @@ const Search = () => {
|
|||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>pomp | {locale == "en" ? "Search" : "검색"}</title>
|
||||
<title>pomp | Search</title>
|
||||
</Helmet>
|
||||
|
||||
<StyledSearch>
|
||||
<h1>{locale == "en" ? "Search" : "검색"}</h1>
|
||||
<h1>Search</h1>
|
||||
|
||||
<StyledSearchContainer>
|
||||
<DateRangeControl>
|
||||
|
@ -235,7 +231,7 @@ const Search = () => {
|
|||
setDateRange(defaultDateRange)
|
||||
}}
|
||||
>
|
||||
{locale == "en" ? "Reset date range" : "날짜 범위 초기화"}
|
||||
Reset date range
|
||||
</ClearDateButton>
|
||||
<StyledDateRange
|
||||
editableDateInputs
|
||||
|
@ -256,15 +252,13 @@ const Search = () => {
|
|||
type="search"
|
||||
value={searchInput}
|
||||
autoComplete="off"
|
||||
placeholder={locale == "en" ? "Search" : "검색"}
|
||||
placeholder="Search"
|
||||
onChange={(event) => setSearchInput(event.target.value)}
|
||||
onKeyPress={(event) => {
|
||||
event.key === "Enter" && searchInput && doSearch()
|
||||
}}
|
||||
/>
|
||||
{locale == "kr" && "결과: "}
|
||||
{postCards.length}{" "}
|
||||
{locale == "en" && (postCards.length > 1 ? "results" : "result")}
|
||||
{postCards.length} result{postCards.length > 1 && "s"}
|
||||
<TagSelect
|
||||
defaultValue={selectedTags}
|
||||
onChange={(newValue) => {
|
||||
|
|
|
@ -32,13 +32,12 @@ interface TagSelectProps {
|
|||
const TagSelect = (props: TagSelectProps) => {
|
||||
const { globalState } = useContext(globalContext)
|
||||
const { theme } = globalState
|
||||
const locale = globalState.locale
|
||||
const { onChange, defaultValue: selectedTags } = props
|
||||
|
||||
return (
|
||||
<StyledReactTagsContainer>
|
||||
<Select
|
||||
placeholder={locale == "en" ? "Select tags..." : "태그를 선택하세요"}
|
||||
placeholder="Select tags..."
|
||||
theme={(reactSelectTheme) => ({
|
||||
...reactSelectTheme,
|
||||
colors: {
|
||||
|
|
|
@ -128,8 +128,7 @@ export interface PortfolioOverview {
|
|||
export interface PortfolioProject {
|
||||
name: string
|
||||
image: string // url to the image
|
||||
overview_en: string
|
||||
overview_kr: string
|
||||
overview: string
|
||||
badges: string[] // array of valid simpleIcons slug
|
||||
repo: string // url of the git repository
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue