minimized use of null, made Page.tsx functional component, removed unused import from NotFound.tsx, and moved HelmetProvider to index.tsx

This commit is contained in:
Kim, Jimin 2021-12-15 10:14:19 +09:00
parent 49fd2b2900
commit d86e5ae002
6 changed files with 251 additions and 284 deletions

View file

@ -5,7 +5,7 @@ import styled, {
createGlobalStyle, createGlobalStyle,
css, css,
} from "styled-components" } from "styled-components"
import { HelmetProvider, Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import storage from "local-storage-fallback" import storage from "local-storage-fallback"
import { isIE } from "react-device-detect" import { isIE } from "react-device-detect"
@ -303,57 +303,47 @@ export default class App extends React.Component<AppProps, AppState> {
) )
return ( return (
<HelmetProvider> <ThemeProvider
<ThemeProvider theme={{
theme={{ currentTheme: this.state.currentTheme,
currentTheme: this.state.currentTheme, setTheme: (setThemeTo) =>
setTheme: (setThemeTo) => this.setState({ currentTheme: setThemeTo }), // make setTheme function available in other components
this.setState({ currentTheme: setThemeTo }), // make setTheme function available in other components }}
}} >
> <Helmet>
<Helmet> <meta property="og:site_name" content="developomp" />
<meta property="og:site_name" content="developomp" />
<meta property="og:title" content="Home" /> <meta property="og:title" content="Home" />
<meta <meta
property="og:description" property="og:description"
content="developomp's blog" content="developomp's blog"
/> />
<meta <meta property="og:url" content={process.env.PUBLIC_URL} />
property="og:url" </Helmet>
content={process.env.PUBLIC_URL}
/>
</Helmet>
<GlobalStyle /> <GlobalStyle />
<Navbar />
<StyledContentContainer>
{this.state.isLoading ? (
<Loading />
) : (
<Routes>
<Route
path="/"
element={
<PostList howMany={5} title="Home" />
}
/>
<Route path="/loading" element={<Loading />} /> <Navbar />
<StyledContentContainer>
<Route path="/search" element={<Search />} /> {this.state.isLoading ? (
<Loading />
<Route path="/404" element={<NotFound />} /> ) : (
<Routes>
<Route path="/:path*" element={<Page />} /> <Route
</Routes> path="/"
)} element={<PostList howMany={5} title="Home" />}
</StyledContentContainer> />
<Footer /> <Route path="/loading" element={<Loading />} />
</ThemeProvider> <Route path="/search" element={<Search />} />
</HelmetProvider> <Route path="/404" element={<NotFound />} />
<Route path="/:path*" element={<Page />} />
</Routes>
)}
</StyledContentContainer>
<Footer />
</ThemeProvider>
) )
} }
} }

View file

@ -65,7 +65,7 @@ const SubMenu = (props: Props) => {
? props.item.iconOpened ? props.item.iconOpened
: props.item.subNav : props.item.subNav
? props.item.iconClosed ? props.item.iconClosed
: null} : undefined}
</div> </div>
</SidebarLink> </SidebarLink>

View file

@ -1,5 +1,6 @@
import React from "react" import React from "react"
import ReactDOM from "react-dom" import ReactDOM from "react-dom"
import { HelmetProvider } from "react-helmet-async"
import { BrowserRouter } from "react-router-dom" import { BrowserRouter } from "react-router-dom"
import App from "./App" import App from "./App"
@ -8,7 +9,9 @@ import reportWebVitals from "./reportWebVitals"
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <HelmetProvider>
<App />
</HelmetProvider>
</BrowserRouter> </BrowserRouter>
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root")

View file

@ -1,4 +1,3 @@
import React from "react"
import styled from "styled-components" import styled from "styled-components"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"

View file

@ -1,19 +1,13 @@
import React from "react" import { useState } from "react"
import { Helmet } from "react-helmet-async" import { Helmet } from "react-helmet-async"
import { Link } from "react-router-dom" import { Link, useLocation } from "react-router-dom"
import styled from "styled-components" import styled from "styled-components"
import { HashLink } from "react-router-hash-link" import { HashLink } from "react-router-hash-link"
import { Collapse } from "react-collapse" import { Collapse } from "react-collapse"
import storage from "local-storage-fallback" import storage from "local-storage-fallback"
import theming from "../theming" import { TocElement, FetchedPage, Map } from "../types/typings"
import Tag from "../components/Tag"
import TagList from "../components/TagList"
import NotFound from "./NotFound"
import Loading from "../components/Loading"
import _map from "../data/map.json"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { import {
faBook, faBook,
@ -23,7 +17,15 @@ import {
faHourglass, faHourglass,
} from "@fortawesome/free-solid-svg-icons" } from "@fortawesome/free-solid-svg-icons"
import { TocElement, FetchedPage, Map } from "../types/typings" import Tag from "../components/Tag"
import TagList from "../components/TagList"
import NotFound from "./NotFound"
import Loading from "../components/Loading"
import theming from "../theming"
import _map from "../data/map.json"
import { useEffect } from "react"
const map: Map = _map const map: Map = _map
@ -119,95 +121,127 @@ function parseToc(tocData: TocElement[]) {
) )
} }
interface PageProps {} const NextPrevButton = (props: { prevURL?: string; nextURL?: string }) => {
return (
interface PageState { <StyledNextPrevContainer>
fetchedPage?: FetchedPage {props.prevURL ? (
isUnsearchable: boolean <StyledLink to={props.prevURL}>prev</StyledLink>
isSeries: boolean ) : (
seriesData: { <StyledDisabledLink>prev</StyledDisabledLink>
seriesHome: string )}
prev: string | null {props.nextURL ? (
next: string | null <StyledLink to={props.nextURL}>next</StyledLink>
} | null ) : (
isTocOpened: boolean <StyledDisabledLink>next</StyledDisabledLink>
loading: boolean )}
</StyledNextPrevContainer>
)
} }
interface NextPrevProps { const PostMeta = (props: { fetchedPage: FetchedPage }) => {
prevURL: string | null return (
nextURL: string | null <StyledMetaContainer>
<FontAwesomeIcon icon={faCalendar} />
&nbsp;&nbsp;&nbsp;
{props.fetchedPage.date || "Unknown date"}
&nbsp;&nbsp;&nbsp;&nbsp;
<FontAwesomeIcon icon={faHourglass} />
&nbsp;&nbsp;&nbsp;
{props.fetchedPage.readTime
? props.fetchedPage.readTime + " read"
: "unknown length"}
&nbsp;&nbsp;&nbsp;&nbsp;
<FontAwesomeIcon icon={faBook} />
&nbsp;&nbsp;&nbsp;
{props.fetchedPage.wordCount
? props.fetchedPage.wordCount + " words"
: "unknown words"}
</StyledMetaContainer>
)
} }
class NextPrev extends React.Component<NextPrevProps> { const PageTOC = (props: { fetchedPage: FetchedPage }) => {
render() { const [isTocOpened, setIsTocOpened] = useState(
return ( storage.getItem("isTocOpened") == "true"
<StyledNextPrevContainer> )
{this.props.prevURL ? (
<StyledLink to={this.props.prevURL}>prev</StyledLink> useEffect(() => {
storage.setItem("isTocOpened", isTocOpened.toString())
}, [isTocOpened])
return (
<>
<StyledTocToggleButton
onClick={() => {
setIsTocOpened((prev) => !prev)
}}
>
<strong>Table of Content </strong>
{isTocOpened ? (
<FontAwesomeIcon icon={faCaretUp} />
) : ( ) : (
<StyledDisabledLink>prev</StyledDisabledLink> <FontAwesomeIcon icon={faCaretDown} />
)} )}
{this.props.nextURL ? ( </StyledTocToggleButton>
<StyledLink to={this.props.nextURL}>next</StyledLink> <StyledCollapseContainer>
) : ( <Collapse isOpened={isTocOpened}>
<StyledDisabledLink>next</StyledDisabledLink> <div className="white-link">{props.fetchedPage.toc}</div>
)} </Collapse>
</StyledNextPrevContainer> </StyledCollapseContainer>
) <hr />
} </>
)
} }
export default class Page extends React.Component<PageProps, PageState> { interface SeriesData {
constructor(props: PageProps) { seriesHome: string
super(props) prev?: string
next?: string
}
this.state = { const Page = () => {
fetchedPage: undefined, const [fetchedPage, setFetchPage] = useState<FetchedPage | undefined>(
isUnsearchable: false, undefined
isSeries: false, )
seriesData: null, const [isPageUnsearchable, setIsPageUnsearchable] = useState(false)
isTocOpened: storage.getItem("isTocOpened") == "true", const [isSeries, setIsSeries] = useState(false)
loading: true, const [seriesData, setSeriesData] = useState<SeriesData | undefined>(
} undefined
)
const [isLoading, setIsLoading] = useState(true)
const location = useLocation()
const fetchContent = async (
isContentUnsearchable: boolean,
url: string
) => {
return isContentUnsearchable
? await import(`../data/content/unsearchable${url}.json`)
: await import(`../data/content${url}.json`)
} }
componentDidUpdate(_: PageProps, prevState: PageState) { useEffect(() => {
if (this.state.isTocOpened !== prevState.isTocOpened) {
storage.setItem("isTocOpened", this.state.isTocOpened.toString())
}
}
async componentDidMount() {
let _isUnsearchable = false
let _isSeries = false let _isSeries = false
const url = location.pathname.replace(/\/$/, "") // remove trailing slash const url = location.pathname.replace(/\/$/, "") // remove trailing slash
if (url.startsWith("/series")) _isSeries = true if (url.startsWith("/series")) _isSeries = true
if (_isSeries) { if (_isSeries) {
const seriesURL = url.slice(0, url.lastIndexOf("/")) const seriesURL = url.slice(0, url.lastIndexOf("/"))
if (seriesURL in map.series) { if (seriesURL in map.series) {
const _curr: number = map.series[seriesURL].order.indexOf(url) const _curr: number = map.series[seriesURL].order.indexOf(url)
let _prev: number | null = _curr - 1 const _prev = _curr - 1
let _next: number | null = _curr + 1 const _next = _curr + 1
if (_prev < 0) _prev = null setSeriesData({
if (_next > map.series[seriesURL].order.length - 1) _next = null seriesHome: seriesURL,
prev:
this.setState({ _prev >= 0
seriesData: { ? map.series[seriesURL].order[_prev]
seriesHome: seriesURL, : undefined,
prev: next:
_prev != null _next < map.series[seriesURL].order.length
? map.series[seriesURL].order[_prev] ? map.series[seriesURL].order[_next]
: null, : undefined,
next:
_next != null
? map.series[seriesURL].order[_next]
: null,
},
}) })
} }
} }
@ -221,167 +255,109 @@ export default class Page extends React.Component<PageProps, PageState> {
tags: [] as string[], tags: [] as string[],
} }
let _isUnsearchable = false
if (!MapPost) { if (!MapPost) {
_isUnsearchable = true _isUnsearchable = true
this.setState({ isUnsearchable: _isUnsearchable }) setIsPageUnsearchable(_isUnsearchable)
if (!map.unsearchable[url]) { if (!map.unsearchable[url]) {
this.setState({ loading: false }) setIsLoading(false)
return return
} }
} }
const fetched_content = _isUnsearchable fetchContent(_isUnsearchable, url).then((fetched_content) => {
? await import(`../data/content/unsearchable${url}.json`) fetchedPage.content = fetched_content.content
: await import(`../data/content${url}.json`) ? fetched_content.content
: "No content"
fetchedPage.toc = fetched_content.toc
? parseToc(fetched_content.toc)
: undefined
fetchedPage.title = _isUnsearchable
? map.unsearchable[url].title
: fetchedPage?.title
? fetchedPage.title
: "No title"
fetchedPage.content = fetched_content.content if (!_isUnsearchable) {
? fetched_content.content fetchedPage.date = fetchedPage?.date
: "No content" ? fetchedPage.date
fetchedPage.toc = fetched_content.toc : "Unknown date"
? parseToc(fetched_content.toc) }
: undefined
fetchedPage.title = _isUnsearchable
? map.unsearchable[url].title
: fetchedPage?.title
? fetchedPage.title
: "No title"
if (!_isUnsearchable) {
fetchedPage.date = fetchedPage?.date
? fetchedPage.date
: "Unknown date"
}
this.setState({ setIsSeries(_isSeries)
isSeries: _isSeries, setFetchPage(fetchedPage)
fetchedPage: fetchedPage, setIsLoading(false)
loading: false,
}) })
} }, [location])
render() { if (isLoading) return <Loading />
if (this.state.loading) return <Loading />
if (!this.state.fetchedPage) return <NotFound />
return ( if (!fetchedPage) return <NotFound />
<>
<Helmet>
<title>pomp | {this.state.fetchedPage.title}</title>
<meta return (
property="og:title" <>
content={this.state.fetchedPage.title} <Helmet>
<title>pomp | {fetchedPage.title}</title>
<meta property="og:title" content={fetchedPage.title} />
<meta property="og:type" content="website" />
<meta
property="og:image"
content={`${process.env.PUBLIC_URL}/icon/icon.svg`}
/>
</Helmet>
<div className="card main-content" style={{ paddingTop: 0 }}>
{isSeries ? (
<NextPrevButton
prevURL={seriesData?.prev}
nextURL={seriesData?.next}
/> />
<meta property="og:type" content="website" /> ) : (
<meta <br />
property="og:image" )}
content={`${process.env.PUBLIC_URL}/icon/icon.svg`} <StyledTitle>{fetchedPage.title}</StyledTitle>
/>
</Helmet>
<div
className="card main-content"
style={{
paddingTop: 0,
}}
>
{this.state.isSeries ? (
<NextPrev
prevURL={this.state.seriesData?.prev || null}
nextURL={this.state.seriesData?.next || null}
/>
) : (
<br />
)}
<StyledTitle>{this.state.fetchedPage.title}</StyledTitle>
<small>
{/* Post tags */} {/* Post tags */}
<small> {!!fetchedPage.tags.length && (
<TagList direction="left"> <TagList direction="left">
{this.state.fetchedPage.tags ? ( {fetchedPage.tags.map((tag) => {
this.state.fetchedPage.tags.map((tag) => { return (
return ( <td key={fetchedPage?.title + tag}>
<td <Tag text={tag} />
key={ </td>
this.state.fetchedPage?.title + )
tag })}
}
>
<Tag text={tag} />
</td>
)
})
) : (
<></>
)}
</TagList> </TagList>
)}
<br /> <br />
{!this.state.isUnsearchable && ( {/* Post metadata */}
<StyledMetaContainer> {!isPageUnsearchable && (
<FontAwesomeIcon icon={faCalendar} /> <PostMeta fetchedPage={fetchedPage} />
&nbsp;&nbsp;&nbsp; )}
{this.state.fetchedPage?.date || "Unknown date"} </small>
&nbsp;&nbsp;&nbsp;&nbsp;
<FontAwesomeIcon icon={faHourglass} />
&nbsp;&nbsp;&nbsp;
{this.state.fetchedPage?.readTime
? this.state.fetchedPage?.readTime + " read"
: "unknown length"}
&nbsp;&nbsp;&nbsp;&nbsp;
<FontAwesomeIcon icon={faBook} />
&nbsp;&nbsp;&nbsp;
{this.state.fetchedPage?.wordCount
? this.state.fetchedPage.wordCount +
" words"
: "unknown words"}
</StyledMetaContainer>
)}
</small>
<hr /> <hr />
{ {/* add table of contents if it exists */}
// add table of contents if it exists {!!fetchedPage.toc?.props.children.length && (
!!this.state.fetchedPage?.toc?.props.children <PageTOC fetchedPage={fetchedPage} />
.length && ( )}
<>
<StyledTocToggleButton
onClick={() => {
this.setState({
isTocOpened:
!this.state.isTocOpened,
})
}}
>
<strong>Table of Content </strong>
{this.state.isTocOpened ? (
<FontAwesomeIcon icon={faCaretUp} />
) : (
<FontAwesomeIcon icon={faCaretDown} />
)}
</StyledTocToggleButton>
<StyledCollapseContainer>
<Collapse isOpened={this.state.isTocOpened}>
<div className="white-link">
{this.state.fetchedPage.toc}
</div>
</Collapse>
</StyledCollapseContainer>
<hr />
</>
)
}
<div {/* page content */}
className="white-link" <div
dangerouslySetInnerHTML={{ className="white-link"
__html: this.state.fetchedPage.content, dangerouslySetInnerHTML={{
}} __html: fetchedPage.content,
/> }}
</div> />
</> </div>
) </>
} )
} }
export default Page

View file

@ -133,16 +133,11 @@ function isDateInRange(
return Date.parse(from) < compareDate && compareDate < Date.parse(to) return Date.parse(from) < compareDate && compareDate < Date.parse(to)
} }
function isSelectedTagsInPost( function isSelectedTagsInPost(selectedTags?: TagsData[], postTags?: string[]) {
selectedTags: TagsData[] | null, if (!selectedTags || selectedTags.length <= 0) return true
postTags: string[] | undefined if (!postTags || postTags.length <= 0) return false
) {
if (selectedTags == null) return true
if (selectedTags.length == 0) return true
if (postTags == undefined) return false // if tag is empty or undefined
// if tag is empty or null
const tagValues = selectedTags.map((value) => value.value) const tagValues = selectedTags.map((value) => value.value)
if (!postTags.every((val) => tagValues.includes(val))) return false if (!postTags.every((val) => tagValues.includes(val))) return false
@ -150,8 +145,8 @@ function isSelectedTagsInPost(
} }
// Search doesn't work on url change if component is not wrapped // Search doesn't work on url change if component is not wrapped
export default () => { const Search = () => {
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement | null>(null)
const navigate = useNavigate() const navigate = useNavigate()
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -179,7 +174,7 @@ export default () => {
const [postCards, setPostCards] = useState<unknown[]>([]) const [postCards, setPostCards] = useState<unknown[]>([])
const [dateRange, setDateRange] = useState<Array<Range>>(defaultDateRange) const [dateRange, setDateRange] = useState<Array<Range>>(defaultDateRange)
const [searchInput, setSearchInput] = useState(query.query) const [searchInput, setSearchInput] = useState(query.query)
const [selectedTags, setSelectedOption] = useState<TagsData[] | null>( const [selectedTags, setSelectedOption] = useState<TagsData[] | undefined>(
query.tags.map((elem) => ({ label: elem, value: elem })) query.tags.map((elem) => ({ label: elem, value: elem }))
) )
@ -273,12 +268,12 @@ export default () => {
} }
// convert Date to YYYY-MM-DD string if it exists // convert Date to YYYY-MM-DD string if it exists
if (item.selection.startDate != null) if (item.selection.startDate)
historyToPush.from = item.selection.startDate historyToPush.from = item.selection.startDate
.toISOString() .toISOString()
.split("T")[0] .split("T")[0]
if (item.selection.endDate != null) if (item.selection.endDate)
historyToPush.to = item.selection.endDate historyToPush.to = item.selection.endDate
.toISOString() .toISOString()
.split("T")[0] .split("T")[0]
@ -348,8 +343,10 @@ export default () => {
interface TagSelectProps { interface TagSelectProps {
query: Query query: Query
selectedTags: TagsData[] | null selectedTags?: TagsData[]
setSelectedOption: React.Dispatch<React.SetStateAction<TagsData[] | null>> setSelectedOption: React.Dispatch<
React.SetStateAction<TagsData[] | undefined>
>
} }
const TagSelect: React.FC<TagSelectProps> = ({ const TagSelect: React.FC<TagSelectProps> = ({
@ -487,3 +484,5 @@ const TagSelect: React.FC<TagSelectProps> = ({
</StyledReactTagsContainer> </StyledReactTagsContainer>
) )
} }
export default Search