commit
cbea3dce0a
|
@ -18,7 +18,7 @@ export const RenderArticleBlock = ({
|
|||
}: {
|
||||
block: LPE.Article.ContentBlock
|
||||
activeId: string | null
|
||||
headingElementsRef: HeadingElementsRef
|
||||
headingElementsRef?: HeadingElementsRef
|
||||
hide?: boolean
|
||||
}) => {
|
||||
switch (block.type) {
|
||||
|
@ -70,7 +70,9 @@ export const RenderArticleBlock = ({
|
|||
variant="body1"
|
||||
component={block.tagName as any}
|
||||
genericFontFamily="sans-serif"
|
||||
className={extractClassFromFirstTag(block.html) || ''}
|
||||
className={`${extractClassFromFirstTag(
|
||||
block.html,
|
||||
)} ${block.classNames.join(' ')}`}
|
||||
id={extractIdFromFirstTag(block.html) || `p-${block.order}`}
|
||||
dangerouslySetInnerHTML={{ __html: extractInnerHtml(block.html) }}
|
||||
/>
|
||||
|
@ -82,7 +84,9 @@ export const RenderArticleBlock = ({
|
|||
variant="body1"
|
||||
component={block.tagName as any}
|
||||
genericFontFamily="sans-serif"
|
||||
className={extractClassFromFirstTag(block.html) || ''}
|
||||
className={`${extractClassFromFirstTag(
|
||||
block.html,
|
||||
)} ${block.classNames.join(' ')}`}
|
||||
id={extractIdFromFirstTag(block.html) || `p-${block.order}`}
|
||||
dangerouslySetInnerHTML={{ __html: extractInnerHtml(block.html) }}
|
||||
/>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { LPE } from '../../types/lpe.types'
|
|||
|
||||
type Props = PropsWithChildren<{
|
||||
block: LPE.Article.TextBlock
|
||||
headingElementsRef: HeadingElementsRef
|
||||
headingElementsRef?: HeadingElementsRef
|
||||
typographyProps?: TypographyProps
|
||||
}>
|
||||
export const ArticleHeading = ({
|
||||
|
@ -24,7 +24,9 @@ export const ArticleHeading = ({
|
|||
extractIdFromFirstTag(block.html) || `${block.tagName}-${block.order}`
|
||||
const refProp = {
|
||||
ref: (ref: any) => {
|
||||
headingElementsRef.current[id] = ref
|
||||
if (headingElementsRef) {
|
||||
headingElementsRef.current[id] = ref
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ const ArticleHeader = ({
|
|||
<ArticleSubtitle
|
||||
variant="body1"
|
||||
genericFontFamily="sans-serif"
|
||||
component="div"
|
||||
component="p"
|
||||
>
|
||||
{subtitle}
|
||||
</ArticleSubtitle>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import React from 'react'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { RenderArticleBlock } from '@/components/Article/Article.Block'
|
||||
|
||||
export type StaticPageProps = React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> & {
|
||||
data: {
|
||||
page: LPE.StaticPage.Document
|
||||
}
|
||||
}
|
||||
|
||||
export const StaticPage: React.FC<StaticPageProps> = ({
|
||||
data,
|
||||
data: { page },
|
||||
...props
|
||||
}) => {
|
||||
const titleBlock = data.page.content.find((block) => {
|
||||
return (
|
||||
block.type === LPE.Post.ContentBlockTypes.Text &&
|
||||
block.classNames &&
|
||||
block.classNames.includes('title')
|
||||
)
|
||||
}) as LPE.Post.TextBlock | undefined
|
||||
|
||||
return (
|
||||
<Root {...props}>
|
||||
<article>
|
||||
{titleBlock && (
|
||||
<Typography variant={'h1'} genericFontFamily={'serif'}>
|
||||
{titleBlock.text}
|
||||
</Typography>
|
||||
)}
|
||||
{data.page.content.map((block, idx) => (
|
||||
<RenderArticleBlock block={block} activeId={null} key={idx} />
|
||||
))}
|
||||
</article>
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
|
||||
const Root = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
#p-2 {
|
||||
font-size: var(--lsd-body1-fontSize) !important;
|
||||
line-height: var(--lsd-body1-lineHeight) !important;
|
||||
}
|
||||
article {
|
||||
width: 700px;
|
||||
> * {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: none;
|
||||
}
|
||||
`
|
|
@ -0,0 +1 @@
|
|||
export * from './StaticPage'
|
|
@ -0,0 +1,87 @@
|
|||
import { CustomNextPage, GetStaticPaths, GetStaticProps } from 'next'
|
||||
import Error from 'next/error'
|
||||
import SEO from '../components/SEO/SEO'
|
||||
import { StaticPage, StaticPageProps } from '../containers/StaticPage'
|
||||
import unbodyApi from '../services/unbody/unbody.service'
|
||||
import NotFoundPage from './404'
|
||||
|
||||
type PageProps = Partial<Pick<StaticPageProps, 'data'>> & {
|
||||
error?: string
|
||||
notFound?: boolean
|
||||
}
|
||||
|
||||
const Page: CustomNextPage<PageProps> = ({
|
||||
data,
|
||||
error,
|
||||
notFound,
|
||||
...props
|
||||
}) => {
|
||||
if (!data) {
|
||||
if (notFound) {
|
||||
return <Error statusCode={400} />
|
||||
}
|
||||
|
||||
return <Error statusCode={500} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
description={
|
||||
data.page.subtitle ||
|
||||
'Logos online publishing and blogging platform for writers and readers.'
|
||||
}
|
||||
title={`${data.page.title} - Logos Press Engine`}
|
||||
/>
|
||||
<StaticPage data={data} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const { data } = await unbodyApi.getStaticPages()
|
||||
return {
|
||||
paths: data.map((page) => ({
|
||||
params: {
|
||||
slug: page.slug,
|
||||
},
|
||||
})),
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps<PageProps> = async (ctx) => {
|
||||
const { data, errors } = await unbodyApi.getStaticPage({
|
||||
slug: ctx.params!.slug as string,
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
if (errors && typeof errors === 'string' && errors.includes('not found')) {
|
||||
return {
|
||||
notFound: true,
|
||||
props: {
|
||||
notFound: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
console.error(errors)
|
||||
return {
|
||||
props: {
|
||||
error: 'Something went wrong!',
|
||||
},
|
||||
notFound: false,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
data: {
|
||||
page: data,
|
||||
},
|
||||
},
|
||||
notFound: false,
|
||||
}
|
||||
}
|
||||
|
||||
export default Page
|
|
@ -0,0 +1,42 @@
|
|||
import { LPE } from '../../../types/lpe.types'
|
||||
import { UnbodyResGoogleDocData } from '../unbody.types'
|
||||
import { UnbodyDataTypeConfig } from './types'
|
||||
|
||||
export const StaticPageDataType: UnbodyDataTypeConfig<
|
||||
UnbodyResGoogleDocData,
|
||||
LPE.StaticPage.Document
|
||||
> = {
|
||||
key: 'StaticPageDocument',
|
||||
objectType: 'GoogleDoc',
|
||||
classes: ['static-page', 'document'],
|
||||
|
||||
isMatch: (helpers, data) => data.pathString.includes('/Static pages/'),
|
||||
|
||||
transform: async (helpers, data) => {
|
||||
const textBlock = helpers.dataTypes.get({
|
||||
objectType: 'TextBlock',
|
||||
})
|
||||
const imageBlock = helpers.dataTypes.get({
|
||||
objectType: 'ImageBlock',
|
||||
})
|
||||
|
||||
const blocks =
|
||||
await helpers.dataTypes.transformMany<LPE.Article.ContentBlock>(
|
||||
[...textBlock, ...imageBlock],
|
||||
[...(data.blocks || [])].sort((a, b) => a.order - b.order),
|
||||
data,
|
||||
)
|
||||
|
||||
return {
|
||||
id: data._additional.id,
|
||||
slug: data.slug,
|
||||
title: data.title,
|
||||
subtitle: data.subtitle || '',
|
||||
summary: data.summary || '',
|
||||
createdAt: data.createdAt || null,
|
||||
modifiedAt: data.modifiedAt || null,
|
||||
content: blocks,
|
||||
type: 'static_page',
|
||||
}
|
||||
},
|
||||
}
|
|
@ -5,6 +5,7 @@ import { ArticleTextBlockDataType } from './ArticleTextBlock.dataType'
|
|||
import { ImageBlockDataType } from './ImageBlock.dataType'
|
||||
import { PodcastEpisodeDataType } from './PodcastEpisodeDocument.dataType'
|
||||
import { PodcastShowDataType } from './PodcastShowDocument.dataType'
|
||||
import { StaticPageDataType } from './StaticPageDocument.dataType'
|
||||
import { TextBlockDataType } from './TextBlock.dataType'
|
||||
import { UnbodyDataTypes } from './UnbodyDataTypes'
|
||||
|
||||
|
@ -17,4 +18,5 @@ export const unbodyDataTypes = new UnbodyDataTypes([
|
|||
ArticleSearchResultItemDataType,
|
||||
PodcastShowDataType,
|
||||
PodcastEpisodeDataType,
|
||||
StaticPageDataType,
|
||||
])
|
||||
|
|
|
@ -49,6 +49,7 @@ export const UnbodyDataTypeKeys = {
|
|||
ArticleSearchResultItem: 'ArticleSearchResultItem',
|
||||
PodcastShowDocument: 'PodcastShowDocument',
|
||||
PodcastEpisodeDocument: 'PodcastEpisodeDocument',
|
||||
StaticPageDocument: 'StaticPageDocument',
|
||||
} as const
|
||||
|
||||
export type UnbodyDataTypeKey =
|
||||
|
@ -61,6 +62,7 @@ export const UnbodyDataTypeClasses = {
|
|||
Episode: 'episode',
|
||||
Document: 'document',
|
||||
Search: 'search',
|
||||
StaticPage: 'static-page',
|
||||
} as const
|
||||
|
||||
export type UnbodyDataTypeClass =
|
||||
|
|
|
@ -36,6 +36,10 @@ const podcastEpisodeDocument = unbodyDataTypes.get({
|
|||
classes: ['podcast', 'document', 'episode'],
|
||||
objectType: 'GoogleDoc',
|
||||
})!
|
||||
const staticPageDocument = unbodyDataTypes.get({
|
||||
classes: ['static-page', 'document'],
|
||||
objectType: 'GoogleDoc',
|
||||
})!
|
||||
|
||||
const sortPosts = (a: LPE.Post.Document, b: LPE.Post.Document) =>
|
||||
(a.type === 'podcast' ? new Date(a.publishedAt) : new Date(a.modifiedAt!)) >
|
||||
|
@ -52,7 +56,8 @@ export class UnbodyService {
|
|||
|
||||
initialData: {
|
||||
posts: LPE.Post.Document[]
|
||||
} = { posts: [] }
|
||||
staticPages: LPE.StaticPage.Document[]
|
||||
} = { posts: [], staticPages: [] }
|
||||
|
||||
constructor(private apiKey: string, private projectId: string) {
|
||||
const cache = new InMemoryCache({
|
||||
|
@ -103,6 +108,7 @@ export class UnbodyService {
|
|||
private _loadInitialData = async () => {
|
||||
const articles: LPE.Article.Data[] = await this.fetchAllArticles()
|
||||
const episodes: LPE.Podcast.Document[] = await this.fetchAllEpisodes()
|
||||
const staticPages = await this.fetchAllStaticPages()
|
||||
|
||||
const posts: LPE.Post.Document[] = [...articles, ...episodes].sort(
|
||||
sortPosts,
|
||||
|
@ -110,9 +116,10 @@ export class UnbodyService {
|
|||
|
||||
this.initialData = {
|
||||
posts,
|
||||
staticPages,
|
||||
}
|
||||
|
||||
this.initialDataPromise.resolve({ posts })
|
||||
this.initialDataPromise.resolve({ posts, staticPages })
|
||||
}
|
||||
|
||||
loadInitialData = async (forced: boolean = false) => {
|
||||
|
@ -123,6 +130,31 @@ export class UnbodyService {
|
|||
return this.initialDataPromise.promise
|
||||
}
|
||||
|
||||
private fetchAllStaticPages = async () => {
|
||||
const result: LPE.StaticPage.Document[] = []
|
||||
|
||||
let skip = 0
|
||||
const limit = 50
|
||||
while (true) {
|
||||
const { data } = await this.findStaticPages({
|
||||
skip,
|
||||
limit,
|
||||
parseContent: false,
|
||||
textBlocks: false,
|
||||
includeDrafts: false,
|
||||
toc: false,
|
||||
})
|
||||
|
||||
result.push(...data)
|
||||
|
||||
if (data.length === 0) break
|
||||
|
||||
skip += 50
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fetchAllEpisodes = async () => {
|
||||
const result: LPE.Podcast.Document[] = []
|
||||
|
||||
|
@ -227,6 +259,112 @@ export class UnbodyService {
|
|||
return GoogleDoc?.[0]?.meta?.count || 0
|
||||
}, 0)
|
||||
|
||||
findStaticPages = ({
|
||||
skip = 0,
|
||||
limit = 10,
|
||||
slug,
|
||||
toc = false,
|
||||
filter,
|
||||
nearObject,
|
||||
textBlocks = false,
|
||||
includeDrafts = false,
|
||||
}: {
|
||||
slug?: string
|
||||
skip?: number
|
||||
limit?: number
|
||||
toc?: boolean
|
||||
filter?: GetPostsQueryVariables['filter']
|
||||
nearObject?: string
|
||||
textBlocks?: boolean
|
||||
parseContent?: boolean
|
||||
includeDrafts?: boolean
|
||||
}) =>
|
||||
this.handleRequest<LPE.StaticPage.Document[]>(async () => {
|
||||
const {
|
||||
data: {
|
||||
Get: { GoogleDoc: docs },
|
||||
},
|
||||
} = await this.client.query({
|
||||
query: GetPostsDocument,
|
||||
variables: {
|
||||
toc,
|
||||
textBlocks,
|
||||
mentions: true,
|
||||
imageBlocks: true,
|
||||
...(nearObject
|
||||
? {
|
||||
nearObject: {
|
||||
id: nearObject,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...this.helpers.args.page(skip, limit),
|
||||
filter: {
|
||||
operator: 'And',
|
||||
operands: [
|
||||
this.helpers.args.wherePath([
|
||||
'Static pages',
|
||||
includeDrafts ? 'published|draft' : 'published',
|
||||
]),
|
||||
...(slug
|
||||
? [
|
||||
{
|
||||
path: ['slug'],
|
||||
operator: 'Equal',
|
||||
valueString: slug,
|
||||
} as GetObjectsGoogleDocWhereInpObj,
|
||||
]
|
||||
: []),
|
||||
...(nearObject
|
||||
? [
|
||||
{
|
||||
path: ['id'],
|
||||
operator: 'NotEqual',
|
||||
valueString: nearObject,
|
||||
} as GetObjectsGoogleDocWhereInpObj,
|
||||
]
|
||||
: []),
|
||||
...(filter ? [filter] : []),
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return unbodyDataTypes.transformMany<LPE.StaticPage.Document>(
|
||||
staticPageDocument,
|
||||
docs,
|
||||
undefined,
|
||||
)
|
||||
}, [])
|
||||
|
||||
getStaticPages = () =>
|
||||
this.handleRequest<LPE.StaticPage.Document[]>(async () => {
|
||||
const { staticPages } = await this.loadInitialData()
|
||||
|
||||
return staticPages
|
||||
}, [])
|
||||
|
||||
getStaticPage = ({
|
||||
slug,
|
||||
includeDrafts = false,
|
||||
}: {
|
||||
slug: string
|
||||
includeDrafts?: boolean
|
||||
}) =>
|
||||
this.handleRequest<LPE.StaticPage.Document | null>(async () => {
|
||||
const { data } = await this.findStaticPages({
|
||||
limit: 1,
|
||||
slug,
|
||||
includeDrafts,
|
||||
textBlocks: true,
|
||||
parseContent: true,
|
||||
})
|
||||
|
||||
if (data.length === 0) throw 'Static page not found!'
|
||||
|
||||
return data[0]
|
||||
}, null)
|
||||
|
||||
getArticles = ({
|
||||
skip = 0,
|
||||
limit = 10,
|
||||
|
|
|
@ -52,7 +52,6 @@ export namespace LPE {
|
|||
} as const
|
||||
|
||||
export type ContentBlockType = DictValues<typeof ContentBlockTypes>
|
||||
|
||||
export const ContentBlockLabels = {
|
||||
Title: 'title',
|
||||
Subtitle: 'subtitle',
|
||||
|
@ -92,6 +91,32 @@ export namespace LPE {
|
|||
export type Document = LPE.Article.Data | LPE.Podcast.Document
|
||||
}
|
||||
|
||||
export namespace StaticPage {
|
||||
export type TextBlock = Post.TextBlock<Metadata>
|
||||
export type ImageBlock = Post.ImageBlock<Metadata>
|
||||
export type ContentBlock = Post.ContentBlock<Metadata>
|
||||
export type Footnote = Post.Footnote
|
||||
export type Footnotes = Post.Footnotes
|
||||
export const ContentBlockLabels = Post.ContentBlockLabels
|
||||
export type ContentBlockLabel = Post.ContentBlockLabel
|
||||
|
||||
export type Metadata = {
|
||||
id: string
|
||||
slug: string
|
||||
title: string
|
||||
summary: string
|
||||
subtitle: string
|
||||
|
||||
createdAt: string | null
|
||||
modifiedAt: string | null
|
||||
type: 'static_page'
|
||||
}
|
||||
|
||||
export type Document = Metadata & {
|
||||
content: Array<Article.ContentBlock>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Article {
|
||||
export type Toc = Post.Toc
|
||||
export type TocItem = Post.TocItem
|
||||
|
|
Loading…
Reference in New Issue