personal-website/components/server/renderNotionContent.tsx
Filipe Medeiros af92c1f19a
fix: images in RSS feed
Signed-off-by: Filipe Medeiros <hello@filipesm.eu>
2023-12-03 12:25:56 +01:00

307 lines
9.6 KiB
TypeScript

import { type BlockObjectResponse } from '@notionhq/client/build/src/api-endpoints'
import clsx from 'clsx'
import Image from 'next/image'
import { type FC } from 'react'
import Callout, { CalloutIcon } from '@/components/server/Callout'
import CodeSnippet, { SyncCodeSnippet } from '@/components/server/CodeSnippet'
import List from '@/components/server/List'
import RichText from '@/components/server/RichText'
import TeX, { SyncTeX } from '@/components/server/TeX'
import { type Block, type BlockType } from '@/lib/notion/types'
import extractCurrentListItems from '@/lib/notion/utils/extractCurrentListItems'
import getProxiedAssetUrl from '@/lib/notion/utils/getProxiedAssetUrl'
import richTextAsPlainText from '@/lib/notion/utils/richTextToPlainText'
export const blockRenderMap: {
[T in BlockType]: FC<{ block: Block<T> }>
} = {
paragraph: ({ block }) => (
<p className="mb-6 text-justify">
<RichText richText={block.paragraph.rich_text} />
</p>
),
heading_1: ({ block }) => (
<>
<h2
id={block.id}
className="font-display mb-6 text-4xl before:-mt-40 before:block before:h-40"
>
<RichText richText={block.heading_1.rich_text} />
</h2>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.has_children && <RenderBlockList blockList={block.children} />}
</>
),
heading_2: ({ block }) => (
<>
<h3
id={block.id}
className="font-display mb-5 text-2xl before:-mt-40 before:block before:h-40"
>
<RichText richText={block.heading_2.rich_text} />
</h3>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.has_children && <RenderBlockList blockList={block.children} />}
</>
),
heading_3: ({ block }) => (
<>
<h4
id={block.id}
className="font-display mb-5 text-xl before:-mt-40 before:block before:h-40"
>
<RichText richText={block.heading_3.rich_text} />
</h4>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.has_children && <RenderBlockList blockList={block.children} />}
</>
),
image: ({ block }) => {
const caption = block.image.caption
const altText = caption[0]?.plain_text.startsWith('alt: ')
? richTextAsPlainText(caption).substring(5)
: richTextAsPlainText(caption)
return (
<figure className="mb-6">
<Image
alt={altText}
className={clsx('h-auto w-full rounded object-cover', {
'mb-2': block.image.caption.length > 0,
})}
src={getProxiedAssetUrl({
blockId: block.id,
lastEditedTime: block.last_edited_time,
})}
width={9999} // TODO
height={9999} // TODO
/>
{block.image.caption.length > 0 && (
<figcaption className="text-sm font-light">
<RichText richText={block.image.caption} />
</figcaption>
)}
</figure>
)
},
bulleted_list_item: ({ block }) => (
<li>
<RichText richText={block.bulleted_list_item.rich_text} />
</li>
),
numbered_list_item: ({ block }) => (
<li>
<RichText richText={block.numbered_list_item.rich_text} />
</li>
),
quote: ({ block }) => (
<blockquote className="border-l-1 mb-6 bg-gradient-to-r from-[#171717] px-10 py-7 text-justify dark:[border-image:linear-gradient(to_bottom,rgba(255,255,255,1),rgba(255,255,255,0))_1]">
<RichText richText={block.quote.rich_text} />
</blockquote>
),
toggle: ({ block }) => (
<details>
<summary>
<RichText richText={block.toggle.rich_text} />
</summary>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.children && <RenderBlockList blockList={block.children} />}
</details>
),
callout: ({ block }) => (
<Callout>
<CalloutIcon icon={block.callout.icon} />
<RichText richText={block.callout.rich_text} />
</Callout>
),
// @ts-expect-error Server Component
equation: ({ block }) => <TeX block math={block.equation.expression} />,
divider: () => (
<hr className="my-9 [border-image:linear-gradient(to_right,#424242,#FFFFFF00)_1]" />
),
code: ({ block }) => (
// @ts-expect-error Server Component
<CodeSnippet
language={block.code.language}
codeRichText={block.code.rich_text}
/>
),
// TODO?
table_of_contents: ({ block }) => <></>,
column_list: ({ block }) => <></>,
column: ({ block }) => <></>,
link_to_page: ({ block }) => <></>,
table: ({ block }) => <></>,
table_row: ({ block }) => <></>,
embed: ({ block }) => <></>,
bookmark: ({ block }) => <></>,
video: ({ block }) => <></>,
pdf: ({ block }) => <></>,
file: ({ block }) => <></>,
audio: ({ block }) => <></>,
link_preview: ({ block }) => <></>,
breadcrumb: ({ block }) => <></>,
synced_block: ({ block }) => <></>,
template: ({ block }) => <></>,
to_do: ({ block }) => <></>,
child_page: ({ block }) => <></>,
child_database: ({ block }) => <></>,
unsupported: ({ block }) => <></>,
}
export const rssBlockRenderMap: typeof blockRenderMap = {
...blockRenderMap,
paragraph: ({ block }) => (
<p className="mb-6 text-justify">
<RichText rss richText={block.paragraph.rich_text} />
</p>
),
heading_1: ({ block }) => (
<>
<h2
id={block.id}
className="font-display mb-6 text-4xl before:-mt-40 before:block before:h-40"
>
<RichText rss richText={block.heading_1.rich_text} />
</h2>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.has_children && <RenderBlockList blockList={block.children} />}
</>
),
heading_2: ({ block }) => (
<>
<h3
id={block.id}
className="font-display mb-5 text-2xl before:-mt-40 before:block before:h-40"
>
<RichText rss richText={block.heading_2.rich_text} />
</h3>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.has_children && <RenderBlockList blockList={block.children} />}
</>
),
heading_3: ({ block }) => (
<>
<h4
id={block.id}
className="font-display mb-5 text-xl before:-mt-40 before:block before:h-40"
>
<RichText rss richText={block.heading_3.rich_text} />
</h4>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.has_children && <RenderBlockList blockList={block.children} />}
</>
),
image: ({ block }) => {
const caption = block.image.caption
const altText = caption[0]?.plain_text.startsWith('alt: ')
? richTextAsPlainText(caption).substring(5)
: richTextAsPlainText(caption)
return (
<figure className="mb-6">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt={altText}
className={clsx('h-auto w-full rounded object-cover', {
'mb-2': block.image.caption.length > 0,
})}
src={getProxiedAssetUrl({
blockId: block.id,
lastEditedTime: block.last_edited_time,
withBaseUrl: true,
})}
/>
{block.image.caption.length > 0 && (
<figcaption className="text-sm font-light">
<RichText rss richText={block.image.caption} />
</figcaption>
)}
</figure>
)
},
bulleted_list_item: ({ block }) => (
<li>
<RichText rss richText={block.bulleted_list_item.rich_text} />
</li>
),
numbered_list_item: ({ block }) => (
<li>
<RichText rss richText={block.numbered_list_item.rich_text} />
</li>
),
quote: ({ block }) => (
<blockquote className="border-l-1 mb-6 bg-gradient-to-r from-[#171717] px-10 py-7 text-justify dark:[border-image:linear-gradient(to_bottom,rgba(255,255,255,1),rgba(255,255,255,0))_1]">
<RichText rss richText={block.quote.rich_text} />
</blockquote>
),
toggle: ({ block }) => (
<details>
<summary>
<RichText rss richText={block.toggle.rich_text} />
</summary>
{/* @ts-expect-error TODO find a way to fix this type */}
{block.children && <RenderBlockList blockList={block.children} />}
</details>
),
callout: ({ block }) => (
<Callout>
<CalloutIcon icon={block.callout.icon} />
<RichText rss richText={block.callout.rich_text} />
</Callout>
),
equation: ({ block }) => <SyncTeX block math={block.equation.expression} />,
code: ({ block }) => (
<SyncCodeSnippet
language={block.code.language}
codeRichText={block.code.rich_text}
/>
),
}
export const RenderBlockList = ({
blockList,
rss = false,
}: {
blockList: BlockObjectResponse[]
rss?: boolean
}) => {
return (
<>
{blockList.map((block, index, list) => {
const isListItem =
block.type === 'bulleted_list_item' ||
block.type === 'numbered_list_item'
if (!isListItem)
return <RenderBlock rss={rss} key={block.id} block={block} />
else if (list[index - 1].type !== block.type)
return (
<List
key={block.id}
as={block.type === 'bulleted_list_item' ? 'ul' : 'ol'}
>
{extractCurrentListItems({ list, currentIndex: index }).map(
(listItem) => (
<RenderBlock rss={rss} key={listItem.id} block={listItem} />
),
)}
</List>
)
})}
</>
)
}
export function RenderBlock({
block,
rss = false,
}: {
block: BlockObjectResponse
rss?: boolean
}) {
const Component = (rss ? rssBlockRenderMap : blockRenderMap)[block.type]
// @ts-expect-error we know this is correct because `block.type` is the name we want to use as a key for `blockRenderMap`
return <Component block={block} />
}