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