Merge branch 'main' into amir-fixes

This commit is contained in:
amir houieh 2023-05-15 11:13:52 +02:00 committed by GitHub
commit e9cfbf8314
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 348 additions and 222 deletions

View File

@ -1,3 +1,6 @@
{ {
"extends": "next/core-web-vitals" "extends": "next/core-web-vitals",
"rules": {
"@next/next/no-img-element": "off"
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 31 KiB

16
public/manifest.json Normal file
View File

@ -0,0 +1,16 @@
{
"short_name": "Logos Press Engine",
"name": "Logos Press Engine",
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff",
"icons": [
{
"src": "./favicon.ico",
"sizes": "64x64",
"type": "image/icon",
"purpose": "any maskable"
}
]
}

View File

@ -23,7 +23,7 @@ export default function ArticleBody({ data }: Props) {
if (resultsNumber !== null) { if (resultsNumber !== null) {
setResultsHelperText(data.article.title) setResultsHelperText(data.article.title)
} }
}, [resultsNumber]) }, [resultsNumber, data.article.title, setResultsHelperText])
const ids = searchResultBlocks?.map((block) => block.doc._additional.id) const ids = searchResultBlocks?.map((block) => block.doc._additional.id)

View File

@ -45,4 +45,5 @@ export const ArticleHeading = ({
const Headline = styled(Typography)` const Headline = styled(Typography)`
white-space: pre-wrap; white-space: pre-wrap;
margin-top: 24px; margin-top: 24px;
margin-bottom: 24px;
` `

View File

@ -3,7 +3,6 @@ import React from 'react'
import { useArticleContainerContext } from '@/containers/ArticleContainer.Context' import { useArticleContainerContext } from '@/containers/ArticleContainer.Context'
import { Typography } from '@acid-info/lsd-react' import { Typography } from '@acid-info/lsd-react'
import styles from './Article.module.css' import styles from './Article.module.css'
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
import { Collapse } from '../Collapse' import { Collapse } from '../Collapse'
import Link from 'next/link' import Link from 'next/link'
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types' import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
@ -13,7 +12,7 @@ type Props = {
} }
export const MobileToc = ({ toc }: Props) => { export const MobileToc = ({ toc }: Props) => {
const { tocId, setTocId } = useArticleContainerContext() const { tocId } = useArticleContainerContext()
return toc?.length > 0 ? ( return toc?.length > 0 ? (
<Collapse className={styles.mobileToc} label="Contents"> <Collapse className={styles.mobileToc} label="Contents">
@ -23,7 +22,7 @@ export const MobileToc = ({ toc }: Props) => {
key={idx} key={idx}
active={tocId ? toc.href.substring(1) === tocId : idx === 0} active={tocId ? toc.href.substring(1) === tocId : idx === 0}
> >
<Typography variant="label2" genericFontFamily="sans-serif"> <CustomTypography variant="label2" genericFontFamily="sans-serif">
{toc.title} {toc.title}
</Typography> </Typography>
</TocItem> </TocItem>
@ -44,8 +43,12 @@ const TocItem = styled(Link)<{ active: boolean }>`
p.active p.active
? 'rgb(var(--lsd-theme-primary))' ? 'rgb(var(--lsd-theme-primary))'
: 'rgb(var(--lsd-theme-secondary))'}; : 'rgb(var(--lsd-theme-secondary))'};
label {
text-decoration: none;
color: ${(p) => color: ${(p) =>
p.active p.active
? 'rgb(var(--lsd-theme-secondary))' ? 'rgb(var(--lsd-theme-secondary))'
: 'rgb(var(--lsd-theme-primary))'}; : 'rgb(var(--lsd-theme-primary))'};
}
` `

View File

@ -31,7 +31,9 @@ const ArticleFooter = ({ data }: { data: ArticlePostData }) => {
const ArticleFooterContainer = styled.div` const ArticleFooterContainer = styled.div`
margin-top: 16px; margin-top: 16px;
& > div:not(:first-child) > div > button {
& > div:not(:first-child) > div > button,
& > div:not(:first-child) > div {
border-top: none; border-top: none;
} }
` `

View File

@ -5,7 +5,7 @@ import { Collapse } from '@/components/Collapse'
const FromSameAuthorsArticles = ({ data }: { data: GoogleDocEnhanced[] }) => const FromSameAuthorsArticles = ({ data }: { data: GoogleDocEnhanced[] }) =>
data.length > 0 ? ( data.length > 0 ? (
<Collapse className={styles.relatedArticles} label="From same authors"> <Collapse className={styles.relatedArticles} label="From The Same Authors">
{data.map((article, idx) => ( {data.map((article, idx) => (
<ArticleReference key={idx} data={article} /> <ArticleReference key={idx} data={article} />
))} ))}

View File

@ -1,15 +1,20 @@
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types' import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
import { Typography } from '@acid-info/lsd-react' import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import { Authors } from '../Authors'
import { AuthorsDirection } from '../Authors/Authors'
import Link from 'next/link'
import { useRouter } from 'next/router'
type Props = { type Props = {
data: GoogleDocEnhanced data: GoogleDocEnhanced
} }
export default function ArticleReference({ export default function ArticleReference({
data: { title, modifiedAt, mentions }, data: { title, modifiedAt, mentions, slug },
...props ...props
}: Props) { }: Props) {
const router = useRouter()
const localDate = new Date(modifiedAt).toLocaleString('en-GB', { const localDate = new Date(modifiedAt).toLocaleString('en-GB', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
@ -17,31 +22,47 @@ export default function ArticleReference({
}) })
return ( return (
<Reference {...props}> <ReferenceLink href={slug} {...props}>
<Typography component="span" variant="body1"> <Typography component="span" variant="body1">
{title} {title}
</Typography> </Typography>
<div> <Info>
<Typography variant="body3" genericFontFamily="sans-serif"> <Authors
{/*TODO we need handle multiple authors for same article*/} flexDirection={AuthorsDirection.ROW}
{mentions[0]?.name} gap={4}
</Typography> mentions={mentions}
email={false}
/>
<Typography variant="body3"></Typography> <Typography variant="body3"></Typography>
<Typography variant="body3" genericFontFamily="sans-serif"> <Typography variant="body3" genericFontFamily="sans-serif">
{localDate} {localDate}
</Typography> </Typography>
</div> </Info>
</Reference> </ReferenceLink>
) )
} }
const Reference = styled.div` const ReferenceLink = styled(Link)`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 8px 14px; padding: 8px 14px;
border-bottom: 1px solid rgb(var(--lsd-border-primary)); border-bottom: 1px solid rgb(var(--lsd-border-primary));
text-decoration: none;
cursor: pointer;
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
&:hover {
text-decoration: underline;
}
`
const Info = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
margin-top: 8px;
` `

View File

@ -2,25 +2,38 @@ import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
import Author from './Author' import Author from './Author'
import styled from '@emotion/styled' import styled from '@emotion/styled'
export enum AuthorsDirection {
COLUMN = 'column',
ROW = 'row',
}
const Authors = ({ const Authors = ({
mentions, mentions,
email, email,
gap = 16,
flexDirection = AuthorsDirection.COLUMN,
}: { }: {
mentions: UnbodyGraphQl.Fragments.MentionItem[] mentions: UnbodyGraphQl.Fragments.MentionItem[]
email: boolean email: boolean
}) => gap?: number
mentions?.length > 0 ? ( flexDirection?: AuthorsDirection
<AuthorsContainer> }) => {
return mentions?.length > 0 ? (
<AuthorsContainer gap={gap} flexDirection={flexDirection}>
{mentions.map((mention) => ( {mentions.map((mention) => (
<Author key={mention.name} mention={mention} email={email} /> <Author key={mention.name} mention={mention} email={email} />
))} ))}
</AuthorsContainer> </AuthorsContainer>
) : null ) : null
}
const AuthorsContainer = styled.div` const AuthorsContainer = styled.div<{
gap: number
flexDirection: AuthorsDirection
}>`
display: flex; display: flex;
flex-direction: column; flex-direction: ${({ flexDirection }) => flexDirection};
gap: 16px; gap: ${({ gap }) => gap}px;
` `
export default Authors export default Authors

View File

@ -1,8 +0,0 @@
.collapse > div > button {
width: 100% !important;
}
.collapse > div {
display: flex;
flex-direction: column;
}

View File

@ -1,21 +1,85 @@
import { CollapseProps, Collapse as LsdCollapse } from '@acid-info/lsd-react' import { ArrowDownIcon, ArrowUpIcon, Typography } from '@acid-info/lsd-react'
import styles from './Collapse.module.css'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import clsx from 'clsx' import clsx from 'clsx'
import { useState } from 'react'
type Props = {
label: string
children: React.ReactNode
className?: string
onClick?: () => void
}
export default function Collapse({ export default function Collapse({
label, label,
children, children,
className, className,
onClick,
...props ...props
}: CollapseProps) { }: Props) {
const [open, setOpen] = useState(true)
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation()
setOpen((prev) => !prev)
onClick && onClick()
}
return ( return (
<LsdCollapse <div {...props} className={clsx(className)}>
label={label} <Header onClick={(e) => handleClick(e)}>
{...props} <Label color="primary" component="label" variant="label1">
className={clsx(styles.collapse, className)} {label}
> </Label>
{children} <Icon>
</LsdCollapse> {open ? (
<ArrowUpIcon color="primary" />
) : (
<ArrowDownIcon color="primary" />
)}
</Icon>
</Header>
{open && <Content>{children}</Content>}
</div>
) )
} }
const Header = styled.div`
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
text-align: center;
cursor: pointer;
background: none;
border: 1px solid rgb(var(--lsd-border-primary));
height: 40px;
padding: 9px 17px;
box-sizing: border-box;
&:focus {
outline: none;
}
`
const Label = styled(Typography)`
cursor: pointer;
margin: auto;
`
const Icon = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
`
const Content = styled.div`
border: 1px solid rgb(var(--lsd-border-primary));
border-top: none;
display: flex;
flex-direction: column;
`

View File

@ -1,24 +1,28 @@
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
import { Typography } from '@acid-info/lsd-react' import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import Link from 'next/link'
import { Authors } from '../Authors'
type Props = { type Props = {
title: string data: any
author: string
} }
const ContentBlockBody = ({ title, author }: Props) => { const ContentBlockBody = ({ data }: Props) => {
const firstDocument = data.document[0]
const mentions =
typeof firstDocument.mentions === 'string'
? JSON.parse(firstDocument.mentions)
: firstDocument.mentions
return ( return (
<BlockBodyContainer> <BlockBodyContainer>
<Link href={`/article/${data.document[0].slug}#p-${data.order}`}>
<Typography variant="body1" component="div" genericFontFamily="serif"> <Typography variant="body1" component="div" genericFontFamily="serif">
{title} {data.document[0].title}
</Typography>
<Typography
variant="body3"
component="div"
genericFontFamily="sans-serif"
>
{author}
</Typography> </Typography>
</Link>
<Authors mentions={mentions} email={false} />
</BlockBodyContainer> </BlockBodyContainer>
) )
} }
@ -27,6 +31,10 @@ const BlockBodyContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
a {
text-decoration: none;
}
` `
export default ContentBlockBody export default ContentBlockBody

View File

@ -1,9 +1,13 @@
import styled from '@emotion/styled' import styled from '@emotion/styled'
import { Typography } from '@acid-info/lsd-react' import { Typography } from '@acid-info/lsd-react'
import { PostClassType } from '../Post/Post'
export enum BlockType {
TEXT = 'text',
IMAGE = 'image',
}
type Props = { type Props = {
type: PostClassType type: BlockType
date: string date: string
} }
@ -11,7 +15,7 @@ const ContentBlockHeader = ({ type, date }: Props) => {
return ( return (
<ContentBlockInfo> <ContentBlockInfo>
<Typography variant="body3" genericFontFamily="sans-serif"> <Typography variant="body3" genericFontFamily="sans-serif">
{type.toUpperCase()} {type === BlockType.TEXT ? 'PARAGRAPH' : 'IMAGE'}
</Typography> </Typography>
<Typography variant="body3"></Typography> <Typography variant="body3"></Typography>
<Typography variant="body3" genericFontFamily="sans-serif"> <Typography variant="body3" genericFontFamily="sans-serif">

View File

@ -1,35 +1,30 @@
import Link from 'next/link' import Link from 'next/link'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import Image from 'next/image'
import { SearchResultItem } from '@/types/data.types' import { SearchResultItem } from '@/types/data.types'
import { UnbodyImageBlock } from '@/lib/unbody/unbody.types' import { UnbodyImageBlock } from '@/lib/unbody/unbody.types'
import { GridItem } from '../Grid/Grid' import { GridItem } from '../Grid/Grid'
import { PostClassType } from '../Post/Post' import ContentBlockHeader, { BlockType } from './ContentBlock.Header'
import ContentBlockHeader from './ContentBlock.Header'
import ContentBlockBody from './ContentBlock.Body' import ContentBlockBody from './ContentBlock.Body'
import { ResponsiveImage } from '../ResponsiveImage/ResponsiveImage'
type Props = Omit<SearchResultItem<UnbodyImageBlock>, 'score'> type Props = Omit<SearchResultItem<UnbodyImageBlock>, 'score'>
const ImageBlock = ({ doc }: Props) => { const ImageBlock = ({ doc }: Props) => {
return ( return (
<CustomGridItem className="w-2"> <CustomGridItem className="w-2">
<BlockLink href={`/article/${doc.document[0].slug}`}> {/* TODO: order not working for images */}
<Container> <Container>
<ImageContainer> <Link href={`/article/${doc.document[0].slug}#p-${doc.order}`}>
<Image fill src={doc.url} alt={doc.alt} /> <ResponsiveImage data={doc} />
</ImageContainer> </Link>
<ContentBlockHeader <ContentBlockHeader
type={PostClassType.ARTICLE} type={BlockType.IMAGE}
date={doc?.document[0].modifiedAt} date={doc?.document[0].modifiedAt}
/> />
<ContentBlockBody <ContentBlockBody data={doc} />
title={doc.document[0].title}
author="Jason Freeman"
/>
</Container> </Container>
</BlockLink>
</CustomGridItem> </CustomGridItem>
) )
} }
@ -40,10 +35,6 @@ const CustomGridItem = styled(GridItem)`
} }
` `
const BlockLink = styled(Link)`
text-decoration: none;
`
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -7,7 +7,7 @@ import { UnbodyTextBlock } from '@/lib/unbody/unbody.types'
import { GridItem } from '../Grid/Grid' import { GridItem } from '../Grid/Grid'
import { Typography } from '@acid-info/lsd-react' import { Typography } from '@acid-info/lsd-react'
import { PostClassType } from '../Post/Post' import { PostClassType } from '../Post/Post'
import ContentBlockHeader from './ContentBlock.Header' import ContentBlockHeader, { BlockType } from './ContentBlock.Header'
import ContentBlockBody from './ContentBlock.Body' import ContentBlockBody from './ContentBlock.Body'
type Props = Omit<SearchResultItem<UnbodyTextBlock>, 'score'> type Props = Omit<SearchResultItem<UnbodyTextBlock>, 'score'>
@ -15,21 +15,16 @@ type Props = Omit<SearchResultItem<UnbodyTextBlock>, 'score'>
const TextBlock = ({ doc }: Props) => { const TextBlock = ({ doc }: Props) => {
return ( return (
<GridItem className="w-4"> <GridItem className="w-4">
<BlockLink href={`/article/${doc.document[0].slug}`}>
<Container> <Container>
<ContentBlockHeader <ContentBlockHeader
type={PostClassType.ARTICLE} type={BlockType.TEXT}
date={doc?.document[0].modifiedAt} date={doc?.document[0].modifiedAt}
/> />
<Typography variant="body2" genericFontFamily="sans-serif"> <Typography variant="body2" genericFontFamily="sans-serif">
{doc.text} {doc.text}
</Typography> </Typography>
<ContentBlockBody <ContentBlockBody data={doc} />
title={doc.document[0].title}
author="Jason Freeman"
/>
</Container> </Container>
</BlockLink>
</GridItem> </GridItem>
) )
} }

View File

@ -9,7 +9,7 @@ type Props = {
const FeaturedPost = ({ post }: Props) => { const FeaturedPost = ({ post }: Props) => {
return ( return (
<Grid> <CustomGrid>
<GridItem className="w-16"> <GridItem className="w-16">
<PostLink href={`/article/${post.slug}`}> <PostLink href={`/article/${post.slug}`}>
<PostWrapper> <PostWrapper>
@ -32,10 +32,14 @@ const FeaturedPost = ({ post }: Props) => {
</PostWrapper> </PostWrapper>
</PostLink> </PostLink>
</GridItem> </GridItem>
</Grid> </CustomGrid>
) )
} }
const CustomGrid = styled(Grid)`
margin-bottom: 108px;
`
const PostWrapper = styled.div` const PostWrapper = styled.div`
margin-top: 16px; margin-top: 16px;
padding: 16px 0; padding: 16px 0;
@ -43,8 +47,4 @@ const PostWrapper = styled.div`
width: 100%; width: 100%;
` `
const PostLink = styled(Link)`
text-decoration: none;
`
export default FeaturedPost export default FeaturedPost

View File

@ -31,6 +31,7 @@ export default function FilterTags(props: FilterTagsProps) {
const Container = styled.div` const Container = styled.div`
padding: 8px 0; padding: 8px 0;
max-width: 100%;
` `
const Tags = styled.div` const Tags = styled.div`

View File

@ -22,6 +22,7 @@ const Container = styled.div`
@media (max-width: 768px) { @media (max-width: 768px) {
align-items: flex-start; align-items: flex-start;
text-align: left;
} }
` `

View File

@ -7,8 +7,6 @@ const Main = ({ children }: PropsWithChildren) => {
} }
const Container = styled.main` const Container = styled.main`
//display: flex;
//justify-content: center;
margin-block: ${uiConfigs.postSectionMargin}px; margin-block: ${uiConfigs.postSectionMargin}px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;

View File

@ -5,6 +5,7 @@ import { SunIcon } from '../Icons/SunIcon'
import { MoonIcon } from '../Icons/MoonIcon' import { MoonIcon } from '../Icons/MoonIcon'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { uiConfigs } from '@/configs/ui.configs' import { uiConfigs } from '@/configs/ui.configs'
import Link from 'next/link'
interface NavbarProps { interface NavbarProps {
isDark: boolean isDark: boolean
@ -12,10 +13,9 @@ interface NavbarProps {
} }
export default function Navbar({ isDark, toggle }: NavbarProps) { export default function Navbar({ isDark, toggle }: NavbarProps) {
const router = useRouter()
return ( return (
<Container> <Container>
<LogosIconContainer onClick={() => router.push('/')}> <LogosIconContainer href={'/'}>
<LogosIcon color="primary" /> <LogosIcon color="primary" />
</LogosIconContainer> </LogosIconContainer>
<Icons> <Icons>
@ -38,10 +38,11 @@ const Container = styled.nav`
border-bottom: 1px solid rgb(var(--lsd-theme-primary)); border-bottom: 1px solid rgb(var(--lsd-theme-primary));
position: fixed; position: fixed;
top: 0; top: 0;
width: calc(100% + 16px); width: 100%;
max-width: ${uiConfigs.maxContainerWidth + 16}px; max-width: ${uiConfigs.maxContainerWidth + 40}px; // TBD
background: rgb(var(--lsd-surface-primary)); background: rgb(var(--lsd-surface-primary));
z-index: 100; z-index: 100;
box-sizing: border-box;
// to center-align logo // to center-align logo
&:last-child { &:last-child {
@ -51,13 +52,13 @@ const Container = styled.nav`
// to center-align logo // to center-align logo
&:before { &:before {
content: 'D'; content: 'D';
width: 54px;
margin: 1px auto 1px 1px; margin: 1px auto 1px 1px;
visibility: hidden; visibility: hidden;
padding: 1px;
} }
` `
const LogosIconContainer = styled.div` const LogosIconContainer = styled(Link)`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@ -1,15 +1,23 @@
import styled from '@emotion/styled' import styled from '@emotion/styled'
import { FilterTags } from '@/components/FilterTags' import { FilterTags } from '@/components/FilterTags'
import { useSearchBarContext } from '@/context/searchbar.context' import { useSearchBarContext } from '@/context/searchbar.context'
import { useRouter } from 'next/router'
export const NavbarFiller = () => { export const NavbarFiller = () => {
const router = useRouter()
const { tags } = useSearchBarContext() const { tags } = useSearchBarContext()
const onTagClick = (tag: string) => {
router.push(`/search?topics=${tag}`)
}
return ( return (
<NavbarFillerContainer> <NavbarFillerContainer>
<FilterTags tags={tags} selectedTags={[]} /> <FilterTags onTagClick={onTagClick} tags={tags} selectedTags={[]} />
</NavbarFillerContainer> </NavbarFillerContainer>
) )
} }
export const NavbarFillerContainer = styled.div` export const NavbarFillerContainer = styled.div`
height: var(--lpe-nav-rendered-height); height: var(--lpe-nav-rendered-height);
margin: auto; margin: auto;

View File

@ -17,6 +17,7 @@ import {
ResponsiveImage, ResponsiveImage,
ResponsiveImageProps, ResponsiveImageProps,
} from '../ResponsiveImage/ResponsiveImage' } from '../ResponsiveImage/ResponsiveImage'
import Link from 'next/link'
export enum PostImageRatio { export enum PostImageRatio {
PORTRAIT = 'portrait', PORTRAIT = 'portrait',
@ -95,22 +96,25 @@ export default function Post({
title, title,
description, description,
mentions, mentions,
slug,
tags = [], tags = [],
}, },
...props ...props
}: PostProps) { }: PostProps) {
const date = new Date(dateStr) const _date = useMemo(() => new Date(dateStr), [dateStr])
const _title = useMemo( const _title = useMemo(
() => ( () => (
<TitleLink href={`/article/${slug}`}>
<CustomTypography <CustomTypography
variant={size === PostSize.SMALL ? 'h4' : 'h2'} variant={size === PostSize.SMALL ? 'h4' : 'h2'}
genericFontFamily="serif" genericFontFamily="serif"
> >
{title} {title}
</CustomTypography> </CustomTypography>
</TitleLink>
), ),
[title, size], [title, size, slug],
) )
const _description = useMemo( const _description = useMemo(
@ -126,19 +130,23 @@ export default function Post({
const _thumbnail = useMemo(() => { const _thumbnail = useMemo(() => {
if (!showImage || !coverImage) return null if (!showImage || !coverImage) return null
if (postType === PostType.BODY) { if (postType === PostType.BODY) {
return <ResponsiveImage {...imageProps} data={coverImage} />
} else {
// TBD
// @jinho not sure what this is for?
return ( return (
<ThumbnailContainer aspectRatio={aspectRatio}> <Link href={`/article/${slug}`}>
<Thumbnail fill src={coverImage.url} alt={coverImage.alt} /> <ResponsiveImage {...imageProps} data={coverImage} />
</Link>
)
} else {
return (
<>
<Link href={`/article/${slug}`}>
<ResponsiveImage data={coverImage} alt={coverImage.alt} />
</Link>
{_title} {_title}
{_description} {_description}
</ThumbnailContainer> </>
) )
} }
}, [showImage, coverImage, aspectRatio, postType, _title, _description]) }, [slug, showImage, coverImage, postType, imageProps, _title, _description])
const _header = useMemo(() => { const _header = useMemo(() => {
if (postType === 'body') if (postType === 'body')
@ -151,7 +159,7 @@ export default function Post({
</Typography> </Typography>
<Typography variant="body3"></Typography> <Typography variant="body3"></Typography>
<Typography variant="body3" genericFontFamily="sans-serif"> <Typography variant="body3" genericFontFamily="sans-serif">
{date.toLocaleString('en-GB', { {_date.toLocaleString('en-GB', {
day: 'numeric', day: 'numeric',
month: 'long', // TODO: Should be uppercase month: 'long', // TODO: Should be uppercase
year: 'numeric', year: 'numeric',
@ -162,7 +170,7 @@ export default function Post({
</div> </div>
</> </>
) )
}, [postType, classType, date, _title]) }, [postType, classType, _date, _title])
return ( return (
<Container {...props}> <Container {...props}>
@ -191,21 +199,6 @@ const Container = styled.div`
gap: 16px; gap: 16px;
` `
// @Jinho, I have implemented the ResponsiveImage component, so I guess this is not needed anymore?
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
overflow: hidden;
`
const Thumbnail = styled(Image)` const Thumbnail = styled(Image)`
object-fit: cover; object-fit: cover;
` `
@ -229,3 +222,7 @@ const PodcastAuthor = styled.div`
align-items: center; align-items: center;
gap: 12px; gap: 12px;
` `
const TitleLink = styled(Link)`
text-decoration: none;
`

View File

@ -90,7 +90,3 @@ const PostWrapper = styled.div`
opacity: 0.5; opacity: 0.5;
} }
` `
const PostLink = styled(Link)`
text-decoration: none;
`

View File

@ -1,21 +1,21 @@
import useIsDarkState from '@/states/isDarkState/isDarkState' import useIsDarkState from '@/states/isDarkState/isDarkState'
import { defaultThemes } from '@acid-info/lsd-react' import { defaultThemes } from '@acid-info/lsd-react'
import NextNProgress from 'nextjs-progressbar' import NextNProgress from 'nextjs-progressbar'
import { useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
export const ProgressBar = () => { export const ProgressBar = () => {
const isDark = useIsDarkState().get() const isDark = useIsDarkState().get()
const getColor = () => { const getColor = useCallback(() => {
if (isDark) { if (isDark) {
return `rgb(${defaultThemes.dark.palette.primary})` return `rgb(${defaultThemes.dark.palette.primary})`
} else { } else {
return `rgb(${defaultThemes.dark.palette.secondary})` return `rgb(${defaultThemes.dark.palette.secondary})`
} }
} }, [isDark])
const [color, setColor] = useState(getColor()) const [color, setColor] = useState(getColor())
useEffect(() => setColor(getColor()), [isDark]) useEffect(() => setColor(getColor()), [isDark, getColor])
return ( return (
<NextNProgress <NextNProgress

View File

@ -19,7 +19,10 @@ export default function RelatedContent({ data }: Props) {
> >
<Grid> <Grid>
{data.data.map( {data.data.map(
(block: SearchResultItem<UnbodyImageBlock | UnbodyTextBlock>) => { (
block: SearchResultItem<UnbodyImageBlock | UnbodyTextBlock>,
idx: number,
) => {
if (!block.doc.document || !block.doc.document[0]) return null if (!block.doc.document || !block.doc.document[0]) return null
let refArticle = null let refArticle = null
@ -28,9 +31,9 @@ export default function RelatedContent({ data }: Props) {
} }
switch (block.doc.__typename) { switch (block.doc.__typename) {
case UnbodyGraphQl.UnbodyDocumentTypeNames.TextBlock: case UnbodyGraphQl.UnbodyDocumentTypeNames.TextBlock:
return <TextBlock doc={block.doc} /> return <TextBlock key={`text-${idx}`} doc={block.doc} />
case UnbodyGraphQl.UnbodyDocumentTypeNames.ImageBlock: { case UnbodyGraphQl.UnbodyDocumentTypeNames.ImageBlock: {
return <ImageBlock doc={block.doc} /> return <ImageBlock key={`image-${idx}`} doc={block.doc} />
} }
} }
}, },

View File

@ -11,12 +11,14 @@ export type ResponsiveImageProps = {
export type Props = { export type Props = {
data: UnbodyImageBlock | ImageBlockEnhanced data: UnbodyImageBlock | ImageBlockEnhanced
alt?: string
} & ResponsiveImageProps } & ResponsiveImageProps
export const ResponsiveImage = ({ export const ResponsiveImage = ({
data, data,
height = '100%', height = '100%',
fill = false, fill = false,
alt = 'alt',
nextImageProps, nextImageProps,
}: Props) => { }: Props) => {
const [loaded, setLoaded] = useState(false) const [loaded, setLoaded] = useState(false)
@ -46,13 +48,8 @@ export const ResponsiveImage = ({
background: 'red', background: 'red',
}} }}
> >
<Image <img src={lazyUrl} alt={alt} />
src={lazyUrl} <Image {...imageProps} alt={alt} />
alt={data.alt}
width={data.width}
height={data.height}
/>
<Image {...imageProps} />
</Container> </Container>
) )
} }

View File

@ -54,10 +54,8 @@ export default function Searchbar(props: SearchbarProps) {
const isArticlePage = router.pathname === '/article/[slug]' const isArticlePage = router.pathname === '/article/[slug]'
const performSearch = async ( const performSearch = useCallback(
q: string = query, async (q: string = query, _filterTags: string[] = filterTags) => {
_filterTags: string[] = filterTags,
) => {
//if it is article page, just call onSearch //if it is article page, just call onSearch
if (isArticlePage) { if (isArticlePage) {
if (onSearch) { if (onSearch) {
@ -77,7 +75,9 @@ export default function Searchbar(props: SearchbarProps) {
undefined, undefined,
{ shallow: true }, { shallow: true },
) )
} },
[isArticlePage, router, filterTags, onSearch, query],
)
useEffect(() => { useEffect(() => {
setQuery(extractQueryFromQuery(router.query)) setQuery(extractQueryFromQuery(router.query))
@ -87,7 +87,7 @@ export default function Searchbar(props: SearchbarProps) {
} else { } else {
setSearchScope(ESearchScope.GLOBAL) setSearchScope(ESearchScope.GLOBAL)
} }
}, [router.query.query, router.query.topics]) }, [router.query, router.query.topics, router.pathname])
const performClear = useCallback(() => { const performClear = useCallback(() => {
if (!isArticlePage) { if (!isArticlePage) {
@ -99,7 +99,7 @@ export default function Searchbar(props: SearchbarProps) {
setFilterTags([]) setFilterTags([])
setActive(false) setActive(false)
onReset && onReset() onReset && onReset()
}, [setQuery, setFilterTags]) }, [isArticlePage, onReset, performSearch, setQuery, setFilterTags])
const handleTagClick = (tag: string) => { const handleTagClick = (tag: string) => {
let newSelectedTags = [...filterTags] let newSelectedTags = [...filterTags]

View File

@ -40,7 +40,7 @@ export function SearchbarContainer({ children, onUnfocus = nope }: Props) {
const SearchBarWrapper = styled.div<Props>` const SearchBarWrapper = styled.div<Props>`
display: block; display: block;
width: calc(100% - 16px); width: 100%;
background: rgb(var(--lsd-surface-primary)); background: rgb(var(--lsd-surface-primary));
border-bottom: 1px solid rgb(var(--lsd-border-primary)); border-bottom: 1px solid rgb(var(--lsd-border-primary));
border-top: 1px solid rgb(var(--lsd-border-primary)); border-top: 1px solid rgb(var(--lsd-border-primary));
@ -53,5 +53,6 @@ const SearchBarWrapper = styled.div<Props>`
position: fixed; position: fixed;
top: ${uiConfigs.navbarRenderedHeight - 1}px; top: ${uiConfigs.navbarRenderedHeight - 1}px;
z-index: 100; z-index: 100;
max-width: ${uiConfigs.maxContainerWidth + 40}px; // TBD
} }
` `

View File

@ -9,7 +9,7 @@ type Props = PropsWithChildren<{
export const Section = ({ title, subtitle, children, ...props }: Props) => { export const Section = ({ title, subtitle, children, ...props }: Props) => {
return ( return (
<section style={{ width: '100%' }} {...props}> <SectionContainer {...props}>
<Container> <Container>
<Typography genericFontFamily="sans-serif" variant="body2"> <Typography genericFontFamily="sans-serif" variant="body2">
{title} {title}
@ -28,10 +28,19 @@ export const Section = ({ title, subtitle, children, ...props }: Props) => {
)} )}
</Container> </Container>
{children} {children}
</section> </SectionContainer>
) )
} }
const SectionContainer = styled.section`
width: 100%;
box-sizing: border-box;
@media (max-width: 768px) {
padding-inline: 16px;
}
`
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -24,7 +24,6 @@ export default function TableOfContents({ contents, ...props }: Props) {
useEffect(() => { useEffect(() => {
const onHashChangeStart = (url: string) => { const onHashChangeStart = (url: string) => {
const hash = url.split('#')[1] const hash = url.split('#')[1]
console.log('hash', contents)
if (hash) { if (hash) {
setTocId(hash) setTocId(hash)
} else { } else {
@ -35,7 +34,7 @@ export default function TableOfContents({ contents, ...props }: Props) {
return () => { return () => {
router.events.off('hashChangeStart', onHashChangeStart) router.events.off('hashChangeStart', onHashChangeStart)
} }
}, [router.events]) }, [setTocId, router.events])
return ( return (
<Container <Container
@ -116,4 +115,8 @@ const TocItem = styled(Link)<{ active: boolean }>`
? '1px solid rgb(var(--lsd-border-primary))' ? '1px solid rgb(var(--lsd-border-primary))'
: '1px solid transparent'}; : '1px solid transparent'};
cursor: pointer; cursor: pointer;
label {
cursor: pointer;
}
` `

View File

@ -1,44 +1,38 @@
import { addTopicsToQuery } from '@/utils/search.utils' import { addTopicsToQuery } from '@/utils/search.utils'
import { Tag } from '@acid-info/lsd-react' import { Tag } from '@acid-info/lsd-react'
import styled from '@emotion/styled' import styled from '@emotion/styled'
import Link from 'next/link'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
const Tags = ({ tags }: { tags: string[] }) => { const Tags = ({ tags }: { tags: string[] }) => {
const router = useRouter() const router = useRouter()
const { query } = router
const onTagClick = (e: React.MouseEvent<HTMLElement>, tag: string) => { const { topics } = query
e.preventDefault()
router.push(
{
pathname: '/search',
query: {
...addTopicsToQuery([tag]),
},
},
undefined,
{ shallow: true },
)
}
return tags.length > 0 ? ( return tags.length > 0 ? (
<TagContainer> <TagsContainer>
{tags.map((tag) => ( {tags.map((tag, idx) => (
<Link key={`tag-${idx}`} href={`/search?topics=${tag}`}>
<Tag <Tag
onClick={(e) => onTagClick(e, tag)}
size="small" size="small"
disabled={false} disabled={false}
key={tag} variant={topics?.includes(tag) ? 'filled' : 'outlined'}
> >
{tag} {tag}
</Tag> </Tag>
</Link>
))} ))}
</TagContainer> </TagsContainer>
) : null ) : null
} }
const TagContainer = styled.div` const TagsContainer = styled.div`
display: flex; display: flex;
gap: 8px; gap: 8px;
a {
text-decoration: none;
}
` `
export default Tags export default Tags

View File

@ -32,7 +32,7 @@ export default function App({ Component, pageProps }: AppLayoutProps) {
return ( return (
<ThemeProvider theme={isDark ? defaultThemes.dark : defaultThemes.light}> <ThemeProvider theme={isDark ? defaultThemes.dark : defaultThemes.light}>
<Head> <Head>
<title>Acid</title> <title>Logos Press Engine</title>
<meta <meta
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"

View File

@ -27,7 +27,7 @@ export default async function handler() {
alignItems: 'center', alignItems: 'center',
}} }}
> >
<img width="1200" height="630" src={srcBlob} /> <img width="1200" height="630" src={srcBlob} alt="og-image" />
</div> </div>
), ),
{ {

View File

@ -72,6 +72,8 @@ export default function SearchPage({
articles.reset(initialArticles) articles.reset(initialArticles)
blocks.reset(initialBlocks) blocks.reset(initialBlocks)
} }
// if we follow the eslint, we will have an infinite loop
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mounted, router.query]) }, [mounted, router.query])
return ( return (

View File

@ -16,6 +16,7 @@ export const getSearchBlocksQuery = (args: UnbodyGetFilters = defaultArgs) =>
text text
tagName tagName
classNames classNames
order
document{ document{
...on GoogleDoc{ ...on GoogleDoc{
title title
@ -36,6 +37,7 @@ export const getSearchBlocksQuery = (args: UnbodyGetFilters = defaultArgs) =>
width width
height height
alt alt
order
document{ document{
...on GoogleDoc{ ...on GoogleDoc{
title title