diff --git a/.vscode/settings.json b/.vscode/settings.json index 82abdbe..cedd8b7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,10 @@ "YYYYMMDD", "developomp", "developomp's", - "dompurify" + "dompurify", + "elasticlunr", + "hljs", + "katex", + "texmath" ] } diff --git a/source/package.json b/source/package.json index d808f7f..cc5ad55 100644 --- a/source/package.json +++ b/source/package.json @@ -11,8 +11,9 @@ "private": true, "license": "MIT", "scripts": { - "generate": "ts-node -O '{\"module\":\"commonjs\"}' ./generate.ts", + "generate": "ts-node -O '{\"module\":\"commonjs\"}' --files ./generate.ts", "start": "yarn generate && react-scripts start", + "quick-start": "react-scripts start", "build": "yarn generate && react-scripts build" }, "dependencies": { @@ -51,8 +52,12 @@ "web-vitals": "^2.1.0" }, "devDependencies": { + "@types/katex": "^0.11.1", + "@types/markdown-it": "^12.2.0", "@types/node": "^16.4.10", "@types/react": "^17.0.15", + "@types/react-collapse": "^5.0.1", + "@types/react-date-range": "^1.1.7", "@types/react-dom": "^17.0.9", "@types/styled-components": "^5.1.11", "@typescript-eslint/eslint-plugin": "^4.28.5", diff --git a/source/src/App.tsx b/source/src/App.tsx index bed9da5..7ca927f 100644 --- a/source/src/App.tsx +++ b/source/src/App.tsx @@ -259,7 +259,7 @@ interface AppState { } export default class App extends React.Component { - constructor(props) { + constructor(props: AppProps) { super(props) this.state = { isLoading: true, @@ -283,7 +283,7 @@ export default class App extends React.Component { } } - componentDidUpdate(_, prevState) { + componentDidUpdate(_: AppProps, prevState: AppState) { if (this.state.currentTheme !== prevState.currentTheme) { // save theme when it is changed storage.setItem("theme", this.state.currentTheme) diff --git a/source/src/components/PostCard.tsx b/source/src/components/PostCard.tsx index 051d240..8d38f75 100644 --- a/source/src/components/PostCard.tsx +++ b/source/src/components/PostCard.tsx @@ -13,6 +13,8 @@ import { faHourglass, } from "@fortawesome/free-solid-svg-icons" +import { Post } from "../types/typings" + const StyledTitle = styled.h1` font-size: 2rem; font-style: bold; @@ -22,7 +24,7 @@ const StyledTitle = styled.h1` const StyledMetaContainer = styled.small` color: ${(props) => theming.theme(props.theme.currentTheme, { - light: "#555", + light: "#444", dark: "lightgrey", })}; ` @@ -56,16 +58,11 @@ const StyledPostCardContent = styled.div` })}; ` +interface _PostDateBase extends Post { + url: string +} interface PostCardProps { - postData: { - url: string - title: string | undefined - preview: string - readTime: string - wordCount: number - tags: string[] - date: string | undefined - } + postData: _PostDateBase } export default class PostCard extends React.Component { diff --git a/source/src/components/Sidebar.tsx b/source/src/components/Sidebar.tsx index 9fa1e5a..be17448 100644 --- a/source/src/components/Sidebar.tsx +++ b/source/src/components/Sidebar.tsx @@ -86,7 +86,7 @@ export default class Sidebar extends React.Component< SidebarProps, SidebarState > { - constructor(props) { + constructor(props: SidebarProps) { super(props) this.state = { isSidebarOpen: false, diff --git a/source/src/components/Spinner.tsx b/source/src/components/Spinner.tsx index 36492a4..f9329b1 100644 --- a/source/src/components/Spinner.tsx +++ b/source/src/components/Spinner.tsx @@ -90,7 +90,7 @@ interface SpinnerProps { export default class Spinner extends React.Component { balls: unknown[] = [] - constructor(props) { + constructor(props: SpinnerProps) { super(props) let keyValue = 0 diff --git a/source/src/components/SubMenu.tsx b/source/src/components/SubMenu.tsx index d6c7021..cf344b6 100644 --- a/source/src/components/SubMenu.tsx +++ b/source/src/components/SubMenu.tsx @@ -50,7 +50,7 @@ export default class SubMenu extends React.Component< SubMenuProps, SubMenuState > { - constructor(props) { + constructor(props: SubMenuProps) { super(props) this.state = { isSubNavOpen: false, diff --git a/source/src/pages/Page.tsx b/source/src/pages/Page.tsx index 9767325..904479c 100644 --- a/source/src/pages/Page.tsx +++ b/source/src/pages/Page.tsx @@ -13,7 +13,7 @@ import TagList from "../components/TagList" import NotFound from "./NotFound" import Spinner from "../components/Spinner" -import map from "../data/map.json" +import _map from "../data/map.json" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faBook, @@ -23,6 +23,10 @@ import { faHourglass, } from "@fortawesome/free-solid-svg-icons" +import { TocElement, FetchedPage, Map } from "../types/typings" + +const map: Map = _map + const StyledTitle = styled.h1` margin-bottom: 1rem; ` @@ -92,7 +96,7 @@ const StyledCollapseContainer = styled.div` } ` -function parseToc(json) { +function parseToc(json: TocElement[]) { return (
    {json.map((elem) => ( @@ -110,8 +114,7 @@ function parseToc(json) { interface PageProps {} interface PageState { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fetchedPage: any + fetchedPage?: FetchedPage isUnsearchable: boolean isSeries: boolean seriesData: { @@ -148,7 +151,7 @@ class NextPrev extends React.Component { } export default class Page extends React.Component { - constructor(props) { + constructor(props: PageProps) { super(props) this.state = { @@ -161,7 +164,7 @@ export default class Page extends React.Component { } } - componentDidUpdate(_, prevState) { + componentDidUpdate(_: PageProps, prevState: PageState) { if (this.state.isTocOpened !== prevState.isTocOpened) { storage.setItem("isTocOpened", this.state.isTocOpened.toString()) } @@ -202,17 +205,19 @@ export default class Page extends React.Component { } // fetch page - let fetchedPage = map.posts[url] + const fetchedPage: FetchedPage = { + ...map.posts[url], + toc: undefined, + content: "", + tags: [] as string[], + } + if (!fetchedPage) { - fetchedPage = map.unsearchable[url] - _isUnsearchable = true - this.setState({ isUnsearchable: true }) + this.setState({ isUnsearchable: _isUnsearchable }) - if (!fetchedPage) { - this.setState({ - loading: false, - }) + if (!map.unsearchable[url]) { + this.setState({ loading: false }) return } } @@ -289,7 +294,7 @@ export default class Page extends React.Component { diff --git a/source/src/pages/PostList.tsx b/source/src/pages/PostList.tsx index 4eea666..5429bdb 100644 --- a/source/src/pages/PostList.tsx +++ b/source/src/pages/PostList.tsx @@ -7,9 +7,12 @@ import styled from "styled-components" import { Helmet } from "react-helmet-async" import theming from "../theming" -import map from "../data/map.json" +import _map from "../data/map.json" import PostCard from "../components/PostCard" +import { Map } from "../types/typings" + +const map: Map = _map const StyledPostList = styled.div` padding-top: 2rem; @@ -44,10 +47,10 @@ export default class PostList extends React.Component< PostListProps, PostListState > { - constructor(props) { + constructor(props: PostListProps) { super(props) - const howMany = props.howMany | 0 + const howMany = props.howMany || 0 const isLimited = howMany ? true : false const h1Text = isLimited ? `Recent Posts` : "All Posts" diff --git a/source/src/pages/Search.tsx b/source/src/pages/Search.tsx index 13bb213..5333756 100644 --- a/source/src/pages/Search.tsx +++ b/source/src/pages/Search.tsx @@ -2,11 +2,11 @@ import { useEffect, useState, useRef } from "react" import styled from "styled-components" import { useLocation, useHistory } from "react-router-dom" import { Helmet } from "react-helmet-async" -import { DateRange } from "react-date-range" +import { DateRange, Range, OnDateRangeChangeProps } from "react-date-range" import queryString from "query-string" // parsing url query import elasticlunr from "elasticlunr" // search engine -import map from "../data/map.json" +import _map from "../data/map.json" import searchIndex from "../data/search.json" import theming from "../theming" @@ -17,6 +17,10 @@ import Tag from "../components/Tag" import TagList from "../components/TagList" import PostCard from "../components/PostCard" +import { Map } from "../types/typings" + +const map: Map = _map + const StyledSearch = styled.div` text-align: center; ` @@ -119,21 +123,13 @@ function _Search() { const defaultDateRange = [ { startDate: new Date(0), - endDate: null, + endDate: undefined, key: "selection", }, ] - const [dateRange, setDateRange] = useState< - Array<{ - startDate: Date | null - endDate: Date | null - key?: string - }> - >(defaultDateRange) - + const [dateRange, setDateRange] = useState>(defaultDateRange) const [postCards, setPostCards] = useState([]) - const [searchInput, setSearchInput] = useState(query.query) function doSearch() { @@ -210,7 +206,8 @@ function _Search() { moveRangeOnFirstSelection={false} retainEndDateOnFirstSelection={true} ranges={dateRange} - onChange={(item) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onChange={(item: OnDateRangeChangeProps) => { const historyToPush = { ...(query.query && { query: query.query, diff --git a/source/src/theming.ts b/source/src/theming.ts index 5d9b478..b89ebda 100644 --- a/source/src/theming.ts +++ b/source/src/theming.ts @@ -6,7 +6,10 @@ import { css } from "styled-components" // not declared in the export object so the export object can refer to it -function theme(currentTheme, values) { +function theme( + currentTheme: string, + values: { [key: string]: string | number } +) { return values[currentTheme] } diff --git a/source/src/types/react-date-range.d.ts b/source/src/types/react-date-range.d.ts new file mode 100644 index 0000000..5e452a7 --- /dev/null +++ b/source/src/types/react-date-range.d.ts @@ -0,0 +1,7 @@ +import "react-date-range" + +declare module "react-date-range" { + export interface DateRangeProps extends Range, CommonCalendarProps { + retainEndDateOnFirstSelection?: boolean | undefined + } +} diff --git a/source/src/types/typings.d.ts b/source/src/types/typings.d.ts new file mode 100644 index 0000000..1736c45 --- /dev/null +++ b/source/src/types/typings.d.ts @@ -0,0 +1,48 @@ +export interface TocElement { + slug: string + content: string +} + +export interface Post { + title: string + preview: string + date: string + readTime: string + wordCount: number + tags?: string[] +} + +export interface Series { + title: string + preview: string + date: string + readTime: string + wordCount: number + order: string[] + length: number +} + +export interface FetchedPage { + title: string + preview: string + date: string + readTime: string + wordCount: number + tags: string[] + toc: JSX.Element | undefined + content: string +} + +interface Map { + date: { [date: string]: string[] } + tags: { [tag: string]: string[] } + meta: { tags: string[] } + posts: { [url: string]: Post } + series: { [url: string]: Series } + unsearchable: { [url: string]: { title: string } } +} + +declare module "*.json" { + const data: Map + export default data +} diff --git a/source/tsconfig.json b/source/tsconfig.json index 84292e0..3f8eebe 100644 --- a/source/tsconfig.json +++ b/source/tsconfig.json @@ -10,12 +10,12 @@ "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, + "noImplicitAny": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, - "include": ["src/**/*", "generate.ts"] + "include": ["src/**/*", "types/**/*", "generate.ts"] } diff --git a/source/types/markdown-it-texmath.d.ts b/source/types/markdown-it-texmath.d.ts new file mode 100644 index 0000000..fe17949 --- /dev/null +++ b/source/types/markdown-it-texmath.d.ts @@ -0,0 +1,4 @@ +declare module "markdown-it-texmath" { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export default function texmath(md: MarkdownIt, ...params: any[]): void +} diff --git a/source/types/markdown-toc.d.ts b/source/types/markdown-toc.d.ts new file mode 100644 index 0000000..eeb9e52 --- /dev/null +++ b/source/types/markdown-toc.d.ts @@ -0,0 +1,4 @@ +declare module "markdown-toc" { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export default function toc(str: string): { json: JSON } +} diff --git a/source/types/read-time-estimate.d.ts b/source/types/read-time-estimate.d.ts new file mode 100644 index 0000000..0f16493 --- /dev/null +++ b/source/types/read-time-estimate.d.ts @@ -0,0 +1,19 @@ +declare module "read-time-estimate" { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export default function toc( + string: string, + customWordTime: number, + customImageTime: number, + chineseKoreanReadTime: number, + imageTags: string[] + ): { + humanizedDuration: string + duration: number + totalWords: number + wordTime: number + totalImages: number + imageTime: number + otherLanguageTimeCharacters: number + otherLanguageTime: number + } +} diff --git a/source/yarn.lock b/source/yarn.lock index e732ff2..6e3abfc 100644 --- a/source/yarn.lock +++ b/source/yarn.lock @@ -1887,6 +1887,30 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/katex@^0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5" + integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg== + +"@types/linkify-it@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" + integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + +"@types/markdown-it@^12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.0.tgz#f609929ac1e50cf0d039473fb331ebc62e313b34" + integrity sha512-YEpywby5S2wt64C2E3bcpLvtIV8BuCj+4AGtL7tU51V8Vr1qwm+cX9gFfWRyclgLC0UK/7w2heYmhymDi+snzw== + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + highlight.js "^10.7.2" + +"@types/mdurl@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + "@types/minimatch@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" @@ -1927,6 +1951,21 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@types/react-collapse@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/react-collapse/-/react-collapse-5.0.1.tgz#078ea1ad15e00ba2063f2e4d8d6760c9375a2023" + integrity sha512-Iq3OrqvzCIP0DmAawU4T2VKH6XAplbjo/D7Qk14mcfQ92plU+OrA2SF10r2XrcFg1Wvya/5f8w1vS29RVpdoLQ== + dependencies: + "@types/react" "*" + +"@types/react-date-range@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@types/react-date-range/-/react-date-range-1.1.7.tgz#75c76896cc45f29b9cacc8a4aa0ee54295bf26b7" + integrity sha512-AqnjUNLloFz6ty60RO9VjYLXZGgw6fcbfR/nsEAGH8gINvHDFjmuy4X22aAEMzTnczEXMgEof3R+i09k2ntOeQ== + dependencies: + "@types/react" "*" + date-fns "^2.16.1" + "@types/react-dom@^17.0.9": version "17.0.9" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" @@ -4222,7 +4261,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^2.23.0: +date-fns@^2.16.1, date-fns@^2.23.0: version "2.23.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9" integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA== @@ -5970,6 +6009,11 @@ highlight.js@*, highlight.js@^11.2.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.2.0.tgz#a7e3b8c1fdc4f0538b93b2dc2ddd53a40c6ab0f0" integrity sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw== +highlight.js@^10.7.2: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"