import { asyncComponent, mediaMapInterface } from "../../types/types"; import { isFullBlock } from "@notionhq/client"; import { BlockObjectResponse, PartialBlockObjectResponse, } from "@notionhq/client/build/src/api-endpoints"; import Image from "next/image"; import { RichText } from "./RichText"; import { getBlocks } from "../../index"; import React from "react"; import { Code } from "./Code"; import { Table } from "./Table"; /** * A recursive component that renders a Notion block and child blocks. * @param mediaMap is an object that notion-on-next generates for you. Find it in public/notion-media/media-map.json. */ export const Block = asyncComponent( async ({ block, blocks, mediaMap, databaseId, pageId, }: { block: BlockObjectResponse | PartialBlockObjectResponse; blocks: (BlockObjectResponse | PartialBlockObjectResponse)[]; mediaMap?: mediaMapInterface; databaseId?: string; pageId?: string; }) => { if (!isFullBlock(block)) { return <>; } let children: React.ReactNode[] | undefined; // Table blocks are handled a bit differently. See Table.tsx if (block.has_children && block.type !== "table") { const childBlocks = await getBlocks(block.id); children = childBlocks?.map( (child: BlockObjectResponse | PartialBlockObjectResponse) => { if (child) { return ( ); } else { // Prevents undefined block error return <>; } } ); } // Add support for any block type here. You can add custom styling wherever you'd like. switch (block.type) { case "heading_1": //@ts-ignore Notion types are incorrect if (block.heading_1.is_toggleable) { return (
{children}
); } return (

); case "heading_2": //@ts-ignore Notion types are incorrect if (block.heading_2.is_toggleable) { return ( <>
{children}
); } return (

); case "heading_3": //@ts-ignore Notion types are incorrect if (block.heading_3.is_toggleable) { return ( <>
{children}
); } return (

); case "paragraph": return (

); case "image": // If Media map does not exist, use the external url or file url from Notion. Be aware that these links expire after 1 hr. https://developers.notion.com/docs/working-with-files-and-media const imageUrl: string = databaseId && pageId && mediaMap ? (mediaMap[databaseId][pageId][block.id] as string) : block.image.type == "external" ? block.image.external.url : block.image.file.url; return (
{"Notion {block.image.caption && ( )}
); case "video": // If Media map does not exist, use the external url or file url from Notion. Be aware that these links expire after 1 hr. https://developers.notion.com/docs/working-with-files-and-media const videoUrl: string = databaseId && pageId && mediaMap ? (mediaMap[databaseId][pageId][block.id] as string) : block.video.type == "external" ? block.video.external.url : block.video.file.url; if (videoUrl) { return (
); } else { return
Video URL not found
; } case "bulleted_list_item": return ( ); case "numbered_list_item": const itemPosition = blocks.findIndex( (blocksBlock) => block.id === blocksBlock.id ); // Count backwards to find the number of numbered_list_item blocks before hitting a non-numbered_list_item block // Notions API does not give any information about the position of the block in the list so we need to calculate it let listNumber = 0; for (let i = itemPosition; i >= 0; i--) { let blocksBlock = blocks[i] as BlockObjectResponse; if (blocksBlock.type === "numbered_list_item") { listNumber++; } else { break; } } return (
  1. {children}
); case "code": return (
{block.code.caption && ( )}
); case "callout": return (
{block.callout.icon?.type === "emoji" ? block.callout.icon.emoji : ""}
); case "column_list": return
{children}
; case "column": return
{children}
; case "quote": return (
); case "divider": return
; case "to_do": return (
); case "toggle": return (
{children}
); case "table": return ; default: return
Block {block.type} not supported
; } } );