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,
css,
} from "styled-components"
import { HelmetProvider, Helmet } from "react-helmet-async"
import { Helmet } from "react-helmet-async"
import storage from "local-storage-fallback"
import { isIE } from "react-device-detect"
@ -303,7 +303,6 @@ export default class App extends React.Component<AppProps, AppState> {
)
return (
<HelmetProvider>
<ThemeProvider
theme={{
currentTheme: this.state.currentTheme,
@ -321,13 +320,11 @@ export default class App extends React.Component<AppProps, AppState> {
content="developomp's blog"
/>
<meta
property="og:url"
content={process.env.PUBLIC_URL}
/>
<meta property="og:url" content={process.env.PUBLIC_URL} />
</Helmet>
<GlobalStyle />
<Navbar />
<StyledContentContainer>
{this.state.isLoading ? (
@ -336,24 +333,17 @@ export default class App extends React.Component<AppProps, AppState> {
<Routes>
<Route
path="/"
element={
<PostList howMany={5} title="Home" />
}
element={<PostList howMany={5} title="Home" />}
/>
<Route path="/loading" element={<Loading />} />
<Route path="/search" element={<Search />} />
<Route path="/404" element={<NotFound />} />
<Route path="/:path*" element={<Page />} />
</Routes>
)}
</StyledContentContainer>
<Footer />
</ThemeProvider>
</HelmetProvider>
)
}
}

View file

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

View file

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

View file

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

View file

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