diff --git a/src/components/Article/Article.Block.tsx b/src/components/Article/Article.Block.tsx index 6d1bd63..a13f0db 100644 --- a/src/components/Article/Article.Block.tsx +++ b/src/components/Article/Article.Block.tsx @@ -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) }} /> diff --git a/src/components/Article/Article.Heading.tsx b/src/components/Article/Article.Heading.tsx index b3950c8..30e223d 100644 --- a/src/components/Article/Article.Heading.tsx +++ b/src/components/Article/Article.Heading.tsx @@ -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 + } }, } diff --git a/src/components/Article/Header/Article.Header.tsx b/src/components/Article/Header/Article.Header.tsx index a3a1727..203e7e1 100644 --- a/src/components/Article/Header/Article.Header.tsx +++ b/src/components/Article/Header/Article.Header.tsx @@ -48,7 +48,7 @@ const ArticleHeader = ({ {subtitle} diff --git a/src/containers/StaticPage/StaticPage.tsx b/src/containers/StaticPage/StaticPage.tsx new file mode 100644 index 0000000..cffb386 --- /dev/null +++ b/src/containers/StaticPage/StaticPage.tsx @@ -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 +> & { + data: { + page: LPE.StaticPage.Document + } +} + +export const StaticPage: React.FC = ({ + 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 ( + +
+ {titleBlock && ( + + {titleBlock.text} + + )} + {data.page.content.map((block, idx) => ( + + ))} +
+
+ ) +} + +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; + } +` diff --git a/src/containers/StaticPage/index.ts b/src/containers/StaticPage/index.ts new file mode 100644 index 0000000..281220a --- /dev/null +++ b/src/containers/StaticPage/index.ts @@ -0,0 +1 @@ +export * from './StaticPage' diff --git a/src/pages/[slug].tsx b/src/pages/[slug].tsx new file mode 100644 index 0000000..cf788f2 --- /dev/null +++ b/src/pages/[slug].tsx @@ -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> & { + error?: string + notFound?: boolean +} + +const Page: CustomNextPage = ({ + data, + error, + notFound, + ...props +}) => { + if (!data) { + if (notFound) { + return + } + + return + } + + return ( + <> + + + + ) +} + +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 = 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 diff --git a/src/services/unbody/dataTypes/StaticPageDocument.dataType.ts b/src/services/unbody/dataTypes/StaticPageDocument.dataType.ts new file mode 100644 index 0000000..f6f483b --- /dev/null +++ b/src/services/unbody/dataTypes/StaticPageDocument.dataType.ts @@ -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( + [...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', + } + }, +} diff --git a/src/services/unbody/dataTypes/dataTypes.ts b/src/services/unbody/dataTypes/dataTypes.ts index 9441145..9f1aeee 100644 --- a/src/services/unbody/dataTypes/dataTypes.ts +++ b/src/services/unbody/dataTypes/dataTypes.ts @@ -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, ]) diff --git a/src/services/unbody/dataTypes/types.ts b/src/services/unbody/dataTypes/types.ts index e1f84df..bb89f96 100644 --- a/src/services/unbody/dataTypes/types.ts +++ b/src/services/unbody/dataTypes/types.ts @@ -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 = diff --git a/src/services/unbody/unbody.service.ts b/src/services/unbody/unbody.service.ts index 37a1e58..e80ebfd 100644 --- a/src/services/unbody/unbody.service.ts +++ b/src/services/unbody/unbody.service.ts @@ -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(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( + staticPageDocument, + docs, + undefined, + ) + }, []) + + getStaticPages = () => + this.handleRequest(async () => { + const { staticPages } = await this.loadInitialData() + + return staticPages + }, []) + + getStaticPage = ({ + slug, + includeDrafts = false, + }: { + slug: string + includeDrafts?: boolean + }) => + this.handleRequest(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, diff --git a/src/types/lpe.types.ts b/src/types/lpe.types.ts index 04ec542..3b34fa1 100644 --- a/src/types/lpe.types.ts +++ b/src/types/lpe.types.ts @@ -52,7 +52,6 @@ export namespace LPE { } as const export type ContentBlockType = DictValues - 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 + export type ImageBlock = Post.ImageBlock + export type ContentBlock = Post.ContentBlock + 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 + } + } + export namespace Article { export type Toc = Post.Toc export type TocItem = Post.TocItem