From 052379afce3b70dff1e3a580526d788f9aa094cb Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Mon, 8 May 2023 09:12:38 +0900 Subject: [PATCH 1/3] feat: implement article page --- .gitignore | 2 + package.json | 1 + src/components/Article/Article.module.css | 10 + src/components/Article/Article.tsx | 250 ++++++++++++++++++ src/components/Article/index.ts | 1 + src/components/Article/tempData/index.tsx | 45 ++++ .../ArticleReference/ArticleReference.tsx | 51 ++++ src/components/ArticleReference/index.ts | 1 + src/components/Collapse/Collapse.module.css | 8 + src/components/Collapse/Collapse.tsx | 21 ++ src/components/Collapse/index.ts | 1 + src/components/Post/Post.tsx | 14 +- .../PostContainer/PostContainer.tsx | 8 +- src/components/Searchbar/Searchbar.tsx | 2 +- .../TableOfContents/TableOfContents.tsx | 82 ++++++ src/components/TableOfContents/index.ts | 1 + src/configs/ui.configs.ts | 1 + src/containers/.placeholder | 0 src/containers/ArticleContainer.Context.ts | 12 + src/containers/ArticleContainer.tsx | 46 ++++ .../ArticleLayout/Article.layout.module.css | 3 + src/layouts/ArticleLayout/Article.layout.tsx | 3 +- src/pages/article/[:slug].tsx | 15 -- src/pages/article/[slug].tsx | 66 +++++ src/queries/getPost.ts | 25 ++ src/services/unbody.service.ts | 7 + 26 files changed, 655 insertions(+), 21 deletions(-) create mode 100644 src/components/Article/Article.module.css create mode 100644 src/components/Article/Article.tsx create mode 100644 src/components/Article/index.ts create mode 100644 src/components/Article/tempData/index.tsx create mode 100644 src/components/ArticleReference/ArticleReference.tsx create mode 100644 src/components/ArticleReference/index.ts create mode 100644 src/components/Collapse/Collapse.module.css create mode 100644 src/components/Collapse/Collapse.tsx create mode 100644 src/components/Collapse/index.ts create mode 100644 src/components/TableOfContents/TableOfContents.tsx create mode 100644 src/components/TableOfContents/index.ts delete mode 100644 src/containers/.placeholder create mode 100644 src/containers/ArticleContainer.Context.ts create mode 100644 src/containers/ArticleContainer.tsx create mode 100644 src/layouts/ArticleLayout/Article.layout.module.css delete mode 100644 src/pages/article/[:slug].tsx create mode 100644 src/pages/article/[slug].tsx create mode 100644 src/queries/getPost.ts diff --git a/.gitignore b/.gitignore index fb30cbd..0bc659e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ yarn-error.log* next-env.d.ts .idea + +.env diff --git a/package.json b/package.json index 81eb900..c43a1a2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/react": "18.0.35", "@types/react-dom": "18.0.11", "axios": "^1.4.0", + "clsx": "^1.2.1", "eslint": "8.38.0", "eslint-config-next": "13.3.0", "graphql": "^16.6.0", diff --git a/src/components/Article/Article.module.css b/src/components/Article/Article.module.css new file mode 100644 index 0000000..d0bf48e --- /dev/null +++ b/src/components/Article/Article.module.css @@ -0,0 +1,10 @@ +.relatedArticles > div > button { + border-top: none !important; +} + +/* temporary breakpoint */ +@media (min-width: 1024px) { + .mobileToc { + display: none !important; + } +} diff --git a/src/components/Article/Article.tsx b/src/components/Article/Article.tsx new file mode 100644 index 0000000..f2a8ced --- /dev/null +++ b/src/components/Article/Article.tsx @@ -0,0 +1,250 @@ +import { Tag, Typography } from '@acid-info/lsd-react' +import styled from '@emotion/styled' +import Image from 'next/image' +import { useMemo } from 'react' +import { PostProps } from '../Post' +import { PostImageRatio, PostImageRatioOptions, PostSize } from '../Post/Post' +import styles from './Article.module.css' +import { Collapse } from '@/components/Collapse' +import { useArticleContainerContext } from '@/containers/ArticleContainer.Context' +import { moreFromAuthor, references, relatedArticles } from './tempData' +import { ArticleReference } from '../ArticleReference' + +export default function Article({ + appearance: { + size = PostSize.SMALL, + aspectRatio = PostImageRatio.LANDSCAPE, + } = {}, + data: { + coverImage = null, + date: dateStr = '', + title, + text, + summary, + author, + tags = [], + toc = [], + }, + ...props +}: PostProps) { + const articleContainer = useArticleContainerContext() + const { tocIndex, setTocIndex } = articleContainer + + const date = new Date(dateStr) + + const _thumbnail = useMemo(() => { + if (!coverImage) return null + + return ( + + + + ) + }, [coverImage]) + + const _text = useMemo( + () => ( + + {text} + + ), + [text], + ) + + const _mobileToc = useMemo( + () => + toc?.length > 0 && ( + + {toc.map((toc, idx) => ( + setTocIndex(idx)} + active={idx === tocIndex} + variant="body3" + key={idx} + > + {toc} + + ))} + + ), + [toc, tocIndex], + ) + + const _references = useMemo( + () => + references?.length > 0 && ( + + {references.map((reference, idx) => ( + + + {idx + 1}. + + + {reference.text} + + + ))} + + ), + [references], + ) + + const _moreFromAuthor = useMemo( + () => + moreFromAuthor?.length > 0 && ( + + {moreFromAuthor.map((article, idx) => ( + + ))} + + ), + [moreFromAuthor], + ) + + const _relatedArticles = useMemo( + () => + relatedArticles?.length > 0 && ( + + {relatedArticles.map((article, idx) => ( + + ))} + + ), + [relatedArticles], + ) + + return ( + +
+ + + 10 minutes read + + + + {date.toLocaleString('en-GB', { + day: 'numeric', + month: 'long', // TODO: Should be uppercase + year: 'numeric', + })} + + +
+ + + {title} + + + {_thumbnail} + + + {summary} + + + {tags.length > 0 && ( + + {tags.map((tag) => ( + + {tag} + + ))} + + )} + + + {author} + + + {_mobileToc} + + {_text} + + {_references} + +
+ {_moreFromAuthor} + {_relatedArticles} +
+
+ ) +} + +const ArticleContainer = styled.article` + display: flex; + position: relative; + flex-direction: column; + gap: 16px; + max-width: 700px; + margin-inline: 5%; + padding-bottom: 50px; + + // temporary breakpoint + @media (max-width: 1024px) { + margin-inline: 16px; + } +` + +const ThumbnailContainer = styled.div<{ + aspectRatio: PostImageRatio +}>` + aspect-ratio: ${(p) => + p.aspectRatio + ? PostImageRatioOptions[p.aspectRatio] + : PostImageRatioOptions[PostImageRatio.PORTRAIT]}; + position: relative; + width: 100%; + height: 100%; + max-height: 458px; // temporary max-height based on the Figma design's max height +` + +const Thumbnail = styled(Image)` + object-fit: cover; +` + +const Row = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + margin-bottom: 8px; +` + +const CustomTypography = styled(Typography)` + text-overflow: ellipsis; + word-break: break-word; + white-space: pre-wrap; +` +const TagContainer = styled.div` + display: flex; + gap: 8px; + overflow-x: auto; +` + +const Content = styled(CustomTypography)<{ active: boolean }>` + padding: 8px 14px; + background-color: ${(p) => + p.active + ? 'rgb(var(--lsd-theme-primary))' + : 'rgb(var(--lsd-theme-secondary))'}; + color: ${(p) => + p.active + ? 'rgb(var(--lsd-theme-secondary))' + : 'rgb(var(--lsd-theme-primary))'}; +` + +const Reference = styled.div` + display: flex; + padding: 8px 14px; + gap: 8px; +` diff --git a/src/components/Article/index.ts b/src/components/Article/index.ts new file mode 100644 index 0000000..ad2cb33 --- /dev/null +++ b/src/components/Article/index.ts @@ -0,0 +1 @@ +export { default as Article } from './Article' diff --git a/src/components/Article/tempData/index.tsx b/src/components/Article/tempData/index.tsx new file mode 100644 index 0000000..758c1aa --- /dev/null +++ b/src/components/Article/tempData/index.tsx @@ -0,0 +1,45 @@ +import { ArticleReferenceType } from '@/components/ArticleReference/ArticleReference' + +// temporary type +export type ReferenceType = { + text: string + link: string +} + +// temporary data +export const references: ReferenceType[] = [ + { + text: 'Szto, Courtney, and Brian Wilson. "Reduce, re-use, re-ride: Bike waste and moving towards a circular economy for sporting goods." International Review for the Sociology of Sport (2022): 10126902221138033', + link: 'https://acid.info/', + }, + { + text: 'ohnson, Rebecca, Alice Kodama, and Regina Willensky. "The complete impact of bicycle use: analyzing the environmental impact and initiative of the bicycle industry." (2014).', + link: 'https://acid.info/', + }, +] + +export const moreFromAuthor: ArticleReferenceType[] = [ + { + title: 'How to Build a Practical Household Bike Generator', + author: 'Jason Freeman', + date: new Date(), + }, + { + title: 'Preventing an Orwellian Future with Privacy-Enhancing Technology', + author: 'Jason Freeman', + date: new Date(), + }, +] + +export const relatedArticles: ArticleReferenceType[] = [ + { + title: 'How to Build a Practical Household Bike Generator', + author: 'Jason Freeman', + date: new Date(), + }, + { + title: 'Preventing an Orwellian Future with Privacy-Enhancing Technology', + author: 'Jason Freeman', + date: new Date(), + }, +] diff --git a/src/components/ArticleReference/ArticleReference.tsx b/src/components/ArticleReference/ArticleReference.tsx new file mode 100644 index 0000000..ef024c7 --- /dev/null +++ b/src/components/ArticleReference/ArticleReference.tsx @@ -0,0 +1,51 @@ +import { Typography } from '@acid-info/lsd-react' +import styled from '@emotion/styled' + +export type ArticleReferenceType = { + title: string + author: string + date: Date +} + +type Props = { + data: ArticleReferenceType +} + +export default function ArticleReference({ + data: { title, author, date }, + ...props +}: Props) { + const localDate = date.toLocaleString('en-GB', { + day: 'numeric', + month: 'long', + year: 'numeric', + }) + + return ( + + + {title} + +
+ + {author} + + + + {localDate} + +
+
+ ) +} + +const Reference = styled.div` + display: flex; + flex-direction: column; + padding: 8px 14px; + border-bottom: 1px solid rgb(var(--lsd-border-primary)); + + &:last-child { + border-bottom: none; + } +` diff --git a/src/components/ArticleReference/index.ts b/src/components/ArticleReference/index.ts new file mode 100644 index 0000000..173e180 --- /dev/null +++ b/src/components/ArticleReference/index.ts @@ -0,0 +1 @@ +export { default as ArticleReference } from './ArticleReference' diff --git a/src/components/Collapse/Collapse.module.css b/src/components/Collapse/Collapse.module.css new file mode 100644 index 0000000..0e5d1b3 --- /dev/null +++ b/src/components/Collapse/Collapse.module.css @@ -0,0 +1,8 @@ +.collapse > div > button { + width: 100% !important; +} + +.collapse > div { + display: flex; + flex-direction: column; +} diff --git a/src/components/Collapse/Collapse.tsx b/src/components/Collapse/Collapse.tsx new file mode 100644 index 0000000..ea88df7 --- /dev/null +++ b/src/components/Collapse/Collapse.tsx @@ -0,0 +1,21 @@ +import { CollapseProps, Collapse as LsdCollapse } from '@acid-info/lsd-react' +import styles from './Collapse.module.css' +import styled from '@emotion/styled' +import clsx from 'clsx' + +export default function Collapse({ + label, + children, + className, + ...props +}: CollapseProps) { + return ( + + {children} + + ) +} diff --git a/src/components/Collapse/index.ts b/src/components/Collapse/index.ts new file mode 100644 index 0000000..d0f23d4 --- /dev/null +++ b/src/components/Collapse/index.ts @@ -0,0 +1 @@ +export { default as Collapse } from './Collapse' diff --git a/src/components/Post/Post.tsx b/src/components/Post/Post.tsx index 08e53b5..c7b2b1f 100644 --- a/src/components/Post/Post.tsx +++ b/src/components/Post/Post.tsx @@ -47,7 +47,10 @@ export type PostDataProps = { description?: string author?: string tags?: string[] - coverImage?: UnbodyImageBlock + coverImage?: UnbodyImageBlock | null + summary?: string + text?: string + toc?: string[] } export const PostImageRatioOptions = { @@ -71,7 +74,14 @@ export default function Post({ aspectRatio = PostImageRatio.LANDSCAPE, showImage = true, } = {}, - data: { coverImage, date: dateStr, title, description, author, tags = [] }, + data: { + coverImage = null, + date: dateStr = '', + title, + description, + author, + tags = [], + }, ...props }: PostProps) { const date = new Date(dateStr) diff --git a/src/components/PostContainer/PostContainer.tsx b/src/components/PostContainer/PostContainer.tsx index fbe4aac..890b612 100644 --- a/src/components/PostContainer/PostContainer.tsx +++ b/src/components/PostContainer/PostContainer.tsx @@ -16,7 +16,11 @@ export default function PostContainer({ }: PostContainerProps) { return (
- {title && ({title})} + {title && ( + + {title} + + )} {postsData.map((post, index) => ( @@ -34,7 +38,7 @@ const Container = styled.div` padding: 16px; gap: 24px; - // temporariy breakpoint + // temporary breakpoint @media (max-width: 768px) { flex-direction: column; } diff --git a/src/components/Searchbar/Searchbar.tsx b/src/components/Searchbar/Searchbar.tsx index 9983b0a..b5d9115 100644 --- a/src/components/Searchbar/Searchbar.tsx +++ b/src/components/Searchbar/Searchbar.tsx @@ -1,6 +1,5 @@ import { TextField, - Autocomplete, IconButton, SearchIcon, CloseIcon, @@ -15,6 +14,7 @@ import styled from '@emotion/styled' export type SearchbarProps = { searchScope?: ESearchScope + className?: string } export default function Searchbar(props: SearchbarProps) { diff --git a/src/components/TableOfContents/TableOfContents.tsx b/src/components/TableOfContents/TableOfContents.tsx new file mode 100644 index 0000000..567baab --- /dev/null +++ b/src/components/TableOfContents/TableOfContents.tsx @@ -0,0 +1,82 @@ +import { uiConfigs } from '@/configs/ui.configs' +import { useArticleContainerContext } from '@/containers/ArticleContainer.Context' +import { useSticky } from '@/utils/ui.utils' +import { Typography } from '@acid-info/lsd-react' +import styled from '@emotion/styled' + +type Props = { + contents: string[] +} + +export default function TableOfContents({ contents, ...props }: Props) { + const articleContainer = useArticleContainerContext() + const { tocIndex, setTocIndex } = articleContainer + const dy = uiConfigs.navbarRenderedHeight + uiConfigs.postMarginTop + + const { sticky, stickyRef, height } = useSticky(dy) + + const handleSectionClick = (index: number) => { + setTocIndex(index) + // TODO: scrollIntoView + } + + return ( + + Contents + {contents.map((content, index) => ( +
handleSectionClick(index)} + key={index} + > + + {content} + +
+ ))} +
+ ) +} + +const Container = styled.aside<{ dy: number; height: number }>` + display: flex; + flex-wrap: wrap; + flex-direction: column; + width: 162px; + box-sizing: border-box; + height: fit-content; + position: sticky; + top: ${(p) => `${p.dy}px`}; + margin-left: 16px; + + &.sticky { + top: ${uiConfigs.navbarRenderedHeight + 78 + 1}px; + z-index: 100; + height: ${(p) => `${p.height}px`}; + } + + // temporary breakpoint + @media (max-width: 1024px) { + display: none; + } +` + +const Title = styled(Typography)` + margin-bottom: 24px; +` + +const Section = styled.section<{ active: boolean }>` + display: flex; + padding: 8px 0 8px 12px; + border-left: ${(p) => + p.active + ? '1px solid rgb(var(--lsd-border-primary))' + : '1px solid transparent'}; + cursor: pointer; +` diff --git a/src/components/TableOfContents/index.ts b/src/components/TableOfContents/index.ts new file mode 100644 index 0000000..40ac94a --- /dev/null +++ b/src/components/TableOfContents/index.ts @@ -0,0 +1 @@ +export { default as TableOfContents } from './TableOfContents' diff --git a/src/configs/ui.configs.ts b/src/configs/ui.configs.ts index 1036bc5..1075dee 100644 --- a/src/configs/ui.configs.ts +++ b/src/configs/ui.configs.ts @@ -1,3 +1,4 @@ export const uiConfigs = { navbarRenderedHeight: 45, + postMarginTop: 78, } diff --git a/src/containers/.placeholder b/src/containers/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/src/containers/ArticleContainer.Context.ts b/src/containers/ArticleContainer.Context.ts new file mode 100644 index 0000000..db3dff3 --- /dev/null +++ b/src/containers/ArticleContainer.Context.ts @@ -0,0 +1,12 @@ +import React from 'react' + +export type ArticleContainerContextType = { + tocIndex: number + setTocIndex: React.Dispatch> +} + +export const ArticleContainerContext = + React.createContext(null as any) + +export const useArticleContainerContext = () => + React.useContext(ArticleContainerContext) diff --git a/src/containers/ArticleContainer.tsx b/src/containers/ArticleContainer.tsx new file mode 100644 index 0000000..ecfdc05 --- /dev/null +++ b/src/containers/ArticleContainer.tsx @@ -0,0 +1,46 @@ +import { Article } from '@/components/Article' +import { TableOfContents } from '@/components/TableOfContents' +import { ArticleProps } from '@/pages/article/[slug]' +import styled from '@emotion/styled' +import { useState } from 'react' +import { uiConfigs } from '@/configs/ui.configs' +import { ArticleContainerContext } from '@/containers/ArticleContainer.Context' + +const ArticleContainer = (props: ArticleProps) => { + const { post } = props + const [tocIndex, setTocIndex] = useState(0) + + return ( + + {typeof post !== 'undefined' ? ( + + +
+ + + ) : ( +
+

Loading

+
+ )} + + ) +} + +const Container = styled.div` + display: flex; + justify-content: center; + margin-top: ${uiConfigs.postMarginTop}px; +` + +const Right = styled.aside` + width: 162px; + // temporary breakpoint + @media (max-width: 1024px) { + display: none; + } +` + +export default ArticleContainer diff --git a/src/layouts/ArticleLayout/Article.layout.module.css b/src/layouts/ArticleLayout/Article.layout.module.css new file mode 100644 index 0000000..2156f5a --- /dev/null +++ b/src/layouts/ArticleLayout/Article.layout.module.css @@ -0,0 +1,3 @@ +.header > nav { + border-bottom: none; +} diff --git a/src/layouts/ArticleLayout/Article.layout.tsx b/src/layouts/ArticleLayout/Article.layout.tsx index 0479971..130fc2a 100644 --- a/src/layouts/ArticleLayout/Article.layout.tsx +++ b/src/layouts/ArticleLayout/Article.layout.tsx @@ -4,12 +4,13 @@ import { PropsWithChildren } from 'react' import { NavbarFiller } from '@/components/Navbar/NavbarFiller' import { Searchbar } from '@/components/Searchbar' import { ESearchScope } from '@/types/ui.types' +import styles from './Article.layout.module.css' export default function ArticleLayout(props: PropsWithChildren) { const isDarkState = useIsDarkState() return ( <> -
+
diff --git a/src/pages/article/[:slug].tsx b/src/pages/article/[:slug].tsx deleted file mode 100644 index f820bd8..0000000 --- a/src/pages/article/[:slug].tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { NextPage } from 'next' -import { ArticleLayout } from '@/layouts/ArticleLayout' -import { ReactNode } from 'react' - -type Props = NextPage<{}> - -const ArticlePage = (props: Props) => { - return
article
-} - -ArticlePage.getLayout = function getLayout(page: ReactNode) { - return {page} -} - -export default ArticlePage diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx new file mode 100644 index 0000000..6dceef8 --- /dev/null +++ b/src/pages/article/[slug].tsx @@ -0,0 +1,66 @@ +import { GetStaticPropsContext } from 'next' +import { ArticleLayout } from '@/layouts/ArticleLayout' +import { ReactNode } from 'react' +import ArticleContainer from '@/containers/ArticleContainer' +import { UnbodyGoogleDoc, UnbodyImageBlock } from '@/lib/unbody/unbody.types' +import { getArticlePost } from '@/services/unbody.service' +import { PostDataProps } from '@/components/Post/Post' + +export type ArticleProps = { + post: PostDataProps + error: string | null +} + +export const getStaticProps = async ({ params }: GetStaticPropsContext) => { + const slug = params?.slug + console.log('slug', slug) // TODO : fetch data based on slug + let post: Partial = {} + let error = null + + try { + const posts = await getArticlePost() + post = posts[0] + } catch (e) { + error = JSON.stringify(e) + } + + return { + props: { + post: { + date: post.modifiedAt, + title: post.title, + summary: post.summary, + text: post.text, + author: 'Jinho', + tags: post.tags, + toc: [ + 'The dangers of totalitarian surveillance', + 'Orwellian Future', + 'Privacy-enhancing technology and its benefits', + 'Ethical considerations of privacy-enhancing technology', + ], + ...(post.blocks && post.blocks!.length > 0 + ? { coverImage: post.blocks![0] as UnbodyImageBlock } + : {}), + }, + error, + }, + } +} + +const ArticlePage = (props: ArticleProps) => { + return +} + +export async function getStaticPaths() { + return { + paths: [{ params: { slug: 'sth' } }], + fallback: true, + } +} + +ArticlePage.getLayout = function getLayout(page: ReactNode) { + return {page} +} + +export default ArticlePage diff --git a/src/queries/getPost.ts b/src/queries/getPost.ts new file mode 100644 index 0000000..67eeb4c --- /dev/null +++ b/src/queries/getPost.ts @@ -0,0 +1,25 @@ +import { GetGoogleDocQuery } from '.' +import { UnbodyExploreArgs } from '@/lib/unbody/unbody.types' + +const defaultArgs: UnbodyExploreArgs = { + limit: 1, + nearText: { concepts: ['home'] }, +} + +export const getArticlePostQuery = (args: UnbodyExploreArgs = defaultArgs) => + GetGoogleDocQuery(args)(` + sourceId + remoteId + title + summary + tags + createdAt + modifiedAt + text + blocks{ + ...on ImageBlock{ + url + alt + } + } + `) diff --git a/src/services/unbody.service.ts b/src/services/unbody.service.ts index bd7938f..567e324 100644 --- a/src/services/unbody.service.ts +++ b/src/services/unbody.service.ts @@ -3,6 +3,7 @@ import { UnbodyGoogleDoc, UnbodyGraphQlResponseGoogleDoc, } from '@/lib/unbody/unbody.types' +import { getArticlePostQuery } from '@/queries/getPost' import { getHomePagePostsQuery } from '@/queries/getPosts' const { UNBODY_API_KEY, UNBODY_LPE_PROJECT_ID } = process.env @@ -23,4 +24,10 @@ export const getHomepagePosts = (): Promise => { .then(({ data }) => data.Get.GoogleDoc) } +export const getArticlePost = (): Promise => { + return unbody + .request(getArticlePostQuery()) + .then(({ data }) => data.Get.GoogleDoc) +} + export default unbody From 8779020f7f91596f615a0b9510b6a596c2a96397 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Mon, 8 May 2023 17:32:16 +0900 Subject: [PATCH 2/3] refactor: update navbar logo css --- src/components/Navbar/Navbar.tsx | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index fefe2cd..9a07e77 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -3,6 +3,7 @@ import { IconButton, Typography } from '@acid-info/lsd-react' import { LogosIcon } from '../Icons/LogosIcon' import { SunIcon } from '../Icons/SunIcon' import { MoonIcon } from '../Icons/MoonIcon' +import { useRouter } from 'next/router' interface NavbarProps { isDark: boolean @@ -10,9 +11,10 @@ interface NavbarProps { } export default function Navbar({ isDark, toggle }: NavbarProps) { + const router = useRouter() return ( - + router.push('/')}> @@ -31,19 +33,34 @@ const Container = styled.nav` display: flex; padding: 8px; align-items: center; - justify-content: space-between; + justify-content: center; border-bottom: 1px solid rgb(var(--lsd-theme-primary)); position: fixed; top: 0; width: calc(100% - 16px); background: rgb(var(--lsd-surface-primary)); z-index: 100; + + // to center-align logo + &:last-child { + margin-left: auto; + } + + // to center-align logo + &:before { + content: 'D'; + margin: 1px auto 1px 1px; + visibility: hidden; + padding: 1px; + } ` const LogosIconContainer = styled.div` display: flex; align-items: center; - margin-left: auto; + justify-content: center; + cursor: pointer; + @media (max-width: 768px) { margin-left: unset; } From 31819407c8f5262df54ccaf2b11fd7c0b1e647db Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Mon, 8 May 2023 18:10:40 +0900 Subject: [PATCH 3/3] refactor: add missing information of articles --- src/components/Article/Article.tsx | 92 ++++++++++++------- src/components/Post/Post.tsx | 11 ++- .../TableOfContents/TableOfContents.tsx | 18 +++- src/pages/article/[slug].tsx | 19 ++-- src/queries/getPost.ts | 6 +- 5 files changed, 95 insertions(+), 51 deletions(-) diff --git a/src/components/Article/Article.tsx b/src/components/Article/Article.tsx index f2a8ced..4428363 100644 --- a/src/components/Article/Article.tsx +++ b/src/components/Article/Article.tsx @@ -11,17 +11,15 @@ import { moreFromAuthor, references, relatedArticles } from './tempData' import { ArticleReference } from '../ArticleReference' export default function Article({ - appearance: { - size = PostSize.SMALL, - aspectRatio = PostImageRatio.LANDSCAPE, - } = {}, + appearance: { aspectRatio = PostImageRatio.LANDSCAPE } = {}, data: { coverImage = null, date: dateStr = '', title, - text, + blocks, summary, author, + authorEmail, tags = [], toc = [], }, @@ -42,19 +40,17 @@ export default function Article({ ) }, [coverImage]) - const _text = useMemo( - () => ( - - {text} - - ), - [text], + // TODO : using typography for the blocks + const _blocks = useMemo( + () => , + [blocks], ) const _mobileToc = useMemo( () => toc?.length > 0 && ( + {/* @ts-ignore */} {toc.map((toc, idx) => ( setTocIndex(idx)} @@ -136,19 +132,13 @@ export default function Article({
- + {title} - </CustomTypography> + {_thumbnail} - + {summary} @@ -162,20 +152,34 @@ export default function Article({ )} - - {author} - + + + {author} + + + {authorEmail} + + {_mobileToc} - {_text} + {_blocks} {_references} -
+ {_moreFromAuthor} {_relatedArticles} -
+ ) } @@ -195,6 +199,22 @@ const ArticleContainer = styled.article` } ` +const CustomTypography = styled(Typography)` + text-overflow: ellipsis; + word-break: break-word; + white-space: pre-wrap; +` + +const Title = styled(CustomTypography)` + margin-bottom: 24px; +` + +const Blocks = styled.div` + white-space: pre-wrap; + margin-top: 24px; + margin-bottom: 80px; +` + const ThumbnailContainer = styled.div<{ aspectRatio: PostImageRatio }>` @@ -219,16 +239,9 @@ const Row = styled.div` gap: 8px; margin-bottom: 8px; ` - -const CustomTypography = styled(Typography)` - text-overflow: ellipsis; - word-break: break-word; - white-space: pre-wrap; -` const TagContainer = styled.div` display: flex; gap: 8px; - overflow-x: auto; ` const Content = styled(CustomTypography)<{ active: boolean }>` @@ -248,3 +261,14 @@ const Reference = styled.div` padding: 8px 14px; gap: 8px; ` + +const ArticleReferences = styled.div` + margin-top: 16px; +` + +const AuthorInfo = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 8px; +` diff --git a/src/components/Post/Post.tsx b/src/components/Post/Post.tsx index c7b2b1f..6d5e8e2 100644 --- a/src/components/Post/Post.tsx +++ b/src/components/Post/Post.tsx @@ -4,7 +4,11 @@ import styled from '@emotion/styled' import Image from 'next/image' import { LogosCircleIcon } from '../Icons/LogosCircleIcon' import { useMemo } from 'react' -import { UnbodyImageBlock } from '@/lib/unbody/unbody.types' +import { + UnbodyGoogleDoc, + UnbodyImageBlock, + UnbodyTextBlock, +} from '@/lib/unbody/unbody.types' export enum PostImageRatio { PORTRAIT = 'portrait', @@ -46,11 +50,12 @@ export type PostDataProps = { title: string description?: string author?: string + authorEmail?: string // TODO: can we get author: { name: string, email: string }? tags?: string[] coverImage?: UnbodyImageBlock | null summary?: string - text?: string - toc?: string[] + blocks?: UnbodyTextBlock + toc?: Pick['toc'] } export const PostImageRatioOptions = { diff --git a/src/components/TableOfContents/TableOfContents.tsx b/src/components/TableOfContents/TableOfContents.tsx index 567baab..cccf2a1 100644 --- a/src/components/TableOfContents/TableOfContents.tsx +++ b/src/components/TableOfContents/TableOfContents.tsx @@ -3,9 +3,12 @@ import { useArticleContainerContext } from '@/containers/ArticleContainer.Contex import { useSticky } from '@/utils/ui.utils' import { Typography } from '@acid-info/lsd-react' import styled from '@emotion/styled' +import { UnbodyGoogleDoc } from '@/lib/unbody/unbody.types' + +export type TableOfContentsProps = Pick type Props = { - contents: string[] + contents?: TableOfContentsProps['toc'] } export default function TableOfContents({ contents, ...props }: Props) { @@ -16,8 +19,14 @@ export default function TableOfContents({ contents, ...props }: Props) { const { sticky, stickyRef, height } = useSticky(dy) const handleSectionClick = (index: number) => { + //@ts-ignore + const section = document.getElementById(contents[index].href.substring(1)) + section?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'nearest', + }) setTocIndex(index) - // TODO: scrollIntoView } return ( @@ -29,14 +38,15 @@ export default function TableOfContents({ contents, ...props }: Props) { className={sticky ? 'sticky' : ''} > Contents - {contents.map((content, index) => ( + {/* @ts-ignore */} + {contents?.map((content, index) => (
handleSectionClick(index)} key={index} > - {content} + {content.title}
))} diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 6dceef8..1fb1a50 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -30,15 +30,16 @@ export const getStaticProps = async ({ params }: GetStaticPropsContext) => { date: post.modifiedAt, title: post.title, summary: post.summary, - text: post.text, - author: 'Jinho', - tags: post.tags, - toc: [ - 'The dangers of totalitarian surveillance', - 'Orwellian Future', - 'Privacy-enhancing technology and its benefits', - 'Ethical considerations of privacy-enhancing technology', - ], + //@ts-ignore + blocks: post.blocks + ?.map((block) => `${block.html}\n`) + .slice(2) + .join(''), // temporary solution for HTML/CSS work + author: 'Cameron Williamson', + authorEmail: 'leo@acid.info', + tags: ['Tools', 'Cyber Punk', 'Docs'], + //@ts-ignore + toc: JSON.parse(post?.toc), ...(post.blocks && post.blocks!.length > 0 ? { coverImage: post.blocks![0] as UnbodyImageBlock } : {}), diff --git a/src/queries/getPost.ts b/src/queries/getPost.ts index 67eeb4c..2ccec39 100644 --- a/src/queries/getPost.ts +++ b/src/queries/getPost.ts @@ -15,11 +15,15 @@ export const getArticlePostQuery = (args: UnbodyExploreArgs = defaultArgs) => tags createdAt modifiedAt - text + toc blocks{ ...on ImageBlock{ url alt } + ... on TextBlock { + footnotes + html + } } `)