feat: implement related articles search view
This commit is contained in:
parent
52778669d7
commit
fa8237303f
|
@ -49,7 +49,6 @@ export default function ArticleBody({ data }: Props) {
|
|||
}, [blocks])
|
||||
|
||||
const _blocks = useMemo(() => {
|
||||
console.log(getContentBlocks(blocks))
|
||||
return getContentBlocks(blocks).map((block, idx) => (
|
||||
<RenderArticleBlock key={'block-' + idx} block={block} />
|
||||
))
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import Link from 'next/link'
|
||||
import { Grid, GridItem } from '../Grid/Grid'
|
||||
import styled from '@emotion/styled'
|
||||
import Post, { PostDataProps } from '../Post/Post'
|
||||
|
||||
type Props = {
|
||||
post: PostDataProps
|
||||
}
|
||||
|
||||
const FeaturedPost = ({ post }: Props) => {
|
||||
return (
|
||||
<Grid>
|
||||
<GridItem className="w-16">
|
||||
<PostLink href={`/article/${post.remoteId}`}>
|
||||
<PostWrapper>
|
||||
<Post data={post} />
|
||||
</PostWrapper>
|
||||
</PostLink>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const PostWrapper = styled.div`
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgb(var(--lsd-theme-primary));
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const PostLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
export default FeaturedPost
|
|
@ -0,0 +1 @@
|
|||
export { default as FeaturedPost } from './FeaturedPost'
|
|
@ -34,6 +34,10 @@ export const GridItem = styled.div`
|
|||
grid-column: span 8;
|
||||
}
|
||||
|
||||
&.w-16 {
|
||||
grid-column: span 16;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-column: span 16 !important;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import styled from '@emotion/styled'
|
||||
import { uiConfigs } from '@/configs/ui.configs'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
const Main = ({ children }: PropsWithChildren) => {
|
||||
return <Container>{children}</Container>
|
||||
}
|
||||
|
||||
const Container = styled.main`
|
||||
margin-block: ${uiConfigs.postSectionMargin}px;
|
||||
`
|
||||
|
||||
export default Main
|
|
@ -0,0 +1 @@
|
|||
export { default as Main } from './Main'
|
|
@ -4,17 +4,33 @@ import { PropsWithChildren } from 'react'
|
|||
|
||||
type Props = PropsWithChildren<{
|
||||
title: string
|
||||
matches?: number
|
||||
}>
|
||||
|
||||
export const Section = (props: any) => {
|
||||
export const Section = ({ title, matches, children, ...props }: Props) => {
|
||||
return (
|
||||
<section>
|
||||
<Title>{props.title}</Title>
|
||||
{props.children}
|
||||
<section {...props}>
|
||||
<Container>
|
||||
<Typography genericFontFamily="sans-serif" variant="body2">
|
||||
{title}
|
||||
</Typography>
|
||||
{matches && (
|
||||
<>
|
||||
<Typography variant="body2">•</Typography>
|
||||
<Typography genericFontFamily="sans-serif" variant="body2">
|
||||
{matches} matches
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const Title = styled(Typography)`
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
`
|
||||
|
|
|
@ -14,7 +14,7 @@ type Props = {
|
|||
export default function TableOfContents({ contents, ...props }: Props) {
|
||||
const articleContainer = useArticleContainerContext()
|
||||
const { tocIndex, setTocIndex } = articleContainer
|
||||
const dy = uiConfigs.navbarRenderedHeight + uiConfigs.postMarginTop
|
||||
const dy = uiConfigs.navbarRenderedHeight + uiConfigs.postSectionMargin
|
||||
|
||||
const { sticky, stickyRef, height } = useSticky<HTMLDivElement>(dy)
|
||||
|
||||
|
@ -86,7 +86,8 @@ const Contents = styled.div<{ height: number }>`
|
|||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
height: calc(
|
||||
100vh - ${uiConfigs.navbarRenderedHeight + uiConfigs.postMarginTop + 40}px
|
||||
100vh -
|
||||
${uiConfigs.navbarRenderedHeight + uiConfigs.postSectionMargin + 40}px
|
||||
);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const uiConfigs = {
|
||||
navbarRenderedHeight: 45,
|
||||
postMarginTop: 78,
|
||||
postSectionMargin: 78,
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ import styled from '@emotion/styled'
|
|||
import { useState } from 'react'
|
||||
import { uiConfigs } from '@/configs/ui.configs'
|
||||
import { ArticleContainerContext } from '@/containers/ArticleContainer.Context'
|
||||
import { UnbodyGoogleDoc } from '@/lib/unbody/unbody.types'
|
||||
import { UnbodyGoogleDoc, UnbodyTocItem } from '@/lib/unbody/unbody.types'
|
||||
import ArticleBody from '@/components/Article/ArticleBody'
|
||||
|
||||
interface Props {
|
||||
data: UnbodyGoogleDoc
|
||||
data: UnbodyGoogleDoc & {
|
||||
toc: UnbodyTocItem[]
|
||||
}
|
||||
}
|
||||
|
||||
const ArticleContainer = (props: Props) => {
|
||||
|
@ -30,7 +32,6 @@ const ArticleContainer = (props: Props) => {
|
|||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: ${uiConfigs.postMarginTop}px;
|
||||
`
|
||||
|
||||
const Right = styled.aside`
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Searchbar } from '@/components/Searchbar'
|
|||
import { ESearchScope } from '@/types/ui.types'
|
||||
import styles from './Article.layout.module.css'
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Main } from '@/components/Main'
|
||||
|
||||
export default function ArticleLayout(props: PropsWithChildren<any>) {
|
||||
const isDarkState = useIsDarkState()
|
||||
|
@ -16,7 +17,7 @@ export default function ArticleLayout(props: PropsWithChildren<any>) {
|
|||
<NavbarFiller />
|
||||
<Searchbar searchScope={ESearchScope.ARTICLE} />
|
||||
</header>
|
||||
<main>{props.children}</main>
|
||||
<Main>{props.children}</Main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Hero } from '@/components/Hero'
|
|||
import { NavbarFiller } from '@/components/Navbar/NavbarFiller'
|
||||
import { Searchbar } from '@/components/Searchbar'
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Main } from '@/components/Main'
|
||||
|
||||
export default function DefaultLayout(props: PropsWithChildren<any>) {
|
||||
const isDarkState = useIsDarkState()
|
||||
|
@ -17,7 +18,7 @@ export default function DefaultLayout(props: PropsWithChildren<any>) {
|
|||
<Hero />
|
||||
<Searchbar />
|
||||
</header>
|
||||
<main>{props.children}</main>
|
||||
<Main>{props.children}</Main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.header > nav {
|
||||
border-bottom: none;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { Navbar } from '@/components/Navbar'
|
||||
import useIsDarkState from '@/states/isDarkState/isDarkState'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { NavbarFiller } from '@/components/Navbar/NavbarFiller'
|
||||
import { Searchbar } from '@/components/Searchbar'
|
||||
import { ESearchScope } from '@/types/ui.types'
|
||||
import styles from './Search.layout.module.css'
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Main } from '@/components/Main'
|
||||
|
||||
export default function SearchLayout(props: PropsWithChildren<any>) {
|
||||
const isDarkState = useIsDarkState()
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
<NavbarFiller />
|
||||
<Searchbar searchScope={ESearchScope.ARTICLE} />
|
||||
</header>
|
||||
<Main>{props.children}</Main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as SearchLayout } from './Search.layout'
|
|
@ -1,12 +1,11 @@
|
|||
import api from '@/services/unbody.service'
|
||||
|
||||
import Post, { PostDataProps } from '@/components/Post/Post'
|
||||
import { PostDataProps } from '@/components/Post/Post'
|
||||
import { PostsList } from '@/components/PostList/PostList'
|
||||
import { Section } from '@/components/Section/Section'
|
||||
import { UnbodyGoogleDoc, UnbodyImageBlock } from '@/lib/unbody/unbody.types'
|
||||
|
||||
import { ESearchStatus } from '@/types/ui.types'
|
||||
import { GetStaticProps } from 'next'
|
||||
import { getArticleCover } from '@/utils/data.utils'
|
||||
import { FeaturedPost } from '@/components/FeaturedPost'
|
||||
|
||||
type Props = {
|
||||
posts: PostDataProps[]
|
||||
|
@ -17,10 +16,9 @@ type Props = {
|
|||
export default function Home({ posts, featured }: Props) {
|
||||
return (
|
||||
<>
|
||||
{/*@TODO @jinho, wht PostContainer should recive an array of postData instead of only One?*/}
|
||||
{featured && (
|
||||
<Section title={'Featured'}>
|
||||
<Post data={featured} />
|
||||
<FeaturedPost post={featured} />
|
||||
</Section>
|
||||
)}
|
||||
<Section title={'Latest posts'}>
|
||||
|
@ -43,24 +41,20 @@ export const getStaticProps = async () => {
|
|||
remoteId: featured.remoteId,
|
||||
date: featured.modifiedAt,
|
||||
title: featured.title,
|
||||
description: featured.summary,
|
||||
description: featured.subtitle, // TODO: summary is not available
|
||||
author: 'Jinho',
|
||||
tags: featured.tags,
|
||||
...(featured.blocks && featured.blocks!.length > 0
|
||||
? { coverImage: featured.blocks![0] as UnbodyImageBlock }
|
||||
: {}),
|
||||
coverImage: getArticleCover(featured.blocks),
|
||||
}
|
||||
: null,
|
||||
posts: posts.map((post) => ({
|
||||
remoteId: post.remoteId,
|
||||
date: post.modifiedAt,
|
||||
title: post.title,
|
||||
description: post.summary,
|
||||
description: post.subtitle, // TODO: summary is not available
|
||||
author: 'Jinho',
|
||||
tags: post.tags,
|
||||
...(post.blocks && post.blocks!.length > 0
|
||||
? { coverImage: post.blocks![0] as UnbodyImageBlock }
|
||||
: {}),
|
||||
coverImage: getArticleCover(post.blocks),
|
||||
})),
|
||||
errors,
|
||||
},
|
||||
|
|
|
@ -21,8 +21,12 @@ import {
|
|||
extractTopicsFromQuery,
|
||||
} from '@/utils/search.utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { SearchLayout } from '@/layouts/SearchLayout'
|
||||
import { Section } from '@/components/Section/Section'
|
||||
import { PostsList } from '@/components/PostList/PostList'
|
||||
import { getArticleCover } from '@/utils/data.utils'
|
||||
|
||||
interface SearchPageProps {
|
||||
articles: SearchResultItem<UnbodyGoogleDoc>[]
|
||||
|
@ -73,24 +77,22 @@ export default function SearchPage({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<section>
|
||||
<strong>Related articles</strong>
|
||||
<hr />
|
||||
<div>
|
||||
{articles.loading && <div>...</div>}
|
||||
{!articles.error && articles.data && articles.data.length > 0 ? (
|
||||
articles.data.map((article: SearchResultItem<UnbodyGoogleDoc>) => (
|
||||
<div key={article.doc.remoteId}>
|
||||
<h2>{article.doc.title}</h2>
|
||||
<p>{article.doc.summary}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div>Nothing found</div>
|
||||
{articles.data?.length && (
|
||||
<Section title={'Related Articles'} matches={articles.data?.length}>
|
||||
<PostsList
|
||||
posts={articles.data.map((article) => ({
|
||||
remoteId: article.doc.remoteId,
|
||||
date: article.doc.modifiedAt,
|
||||
title: article.doc.title,
|
||||
description: article.doc.subtitle, // TODO: summary is not available
|
||||
author: 'Jinho',
|
||||
tags: article.doc.tags,
|
||||
coverImage: getArticleCover(article.doc.blocks),
|
||||
}))}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<br />
|
||||
|
||||
<section>
|
||||
<strong>Related content blocks</strong>
|
||||
<hr />
|
||||
|
@ -156,6 +158,10 @@ export default function SearchPage({
|
|||
)
|
||||
}
|
||||
|
||||
SearchPage.getLayout = function getLayout(page: ReactNode) {
|
||||
return <SearchLayout>{page}</SearchLayout>
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const { data: articles = [] } = await unbodyApi.searchArticles()
|
||||
const { data: blocks = [] } = await unbodyApi.serachBlocks()
|
||||
|
|
|
@ -10,6 +10,7 @@ export const getArticlePostQuery = (args: UnbodyGetFilters = defaultArgs) =>
|
|||
sourceId
|
||||
remoteId
|
||||
title
|
||||
subtitle
|
||||
summary
|
||||
tags
|
||||
createdAt
|
||||
|
|
|
@ -12,6 +12,7 @@ export const getHomePagePostsQuery = (args: UnbodyGetFilters = defaultArgs) =>
|
|||
GetGoogleDocQuery(args)(`
|
||||
remoteId
|
||||
title
|
||||
subtitle
|
||||
summary
|
||||
tags
|
||||
createdAt
|
||||
|
@ -21,6 +22,8 @@ export const getHomePagePostsQuery = (args: UnbodyGetFilters = defaultArgs) =>
|
|||
...on ImageBlock{
|
||||
url
|
||||
alt
|
||||
order
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
|
|
@ -7,9 +7,18 @@ export const getSearchArticlesQuery = (args: UnbodyGetFilters = defaultArgs) =>
|
|||
GetGoogleDocQuery(args)(`
|
||||
remoteId
|
||||
title
|
||||
subtitle
|
||||
summary
|
||||
tags
|
||||
modifiedAt
|
||||
blocks{
|
||||
...on ImageBlock{
|
||||
url
|
||||
alt
|
||||
order
|
||||
__typename
|
||||
}
|
||||
}
|
||||
_additional{
|
||||
certainty
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue