Skip to content

Commit

Permalink
feat: add color support and children rendering for todo, list, toggle…
Browse files Browse the repository at this point in the history
… component
  • Loading branch information
alex-guoba committed Mar 5, 2024
1 parent 548fa1b commit 5999897
Show file tree
Hide file tree
Showing 17 changed files with 295 additions and 166 deletions.
59 changes: 30 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,35 +42,36 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the

Most common block types are supported. But some blocks information not supported in Public API, So we give an abbreviated rendering.

| Block Type | Supported | Notes |
|------------------------|------------|---------------------------------------|
| Paragraph | ✅ Yes | |
| Headings | ✅ Yes | |
| Bookmark | ✅ Yes | Use unfurl.js |
| Breadcrumb | ❌ Missing | Not planned |
| Bulleted List | ✅ Yes | |
| Callout | ✅ Yes | |
| Child Databases | ❌ Missing | Not planned. |
| Child page | ✅ Yes | |
| Code | ✅ Yes | Use [shiki](https://shiki.style/) |
| Column list and column | ✅ Yes | |
| Divider | ❌ Missing | API Unsupported. |
| Embed | ✅ Yes | |
| Equation | ✅ Yes | Use [katex ](https://katex.org/) |
| File | ✅ Yes | |
| Image | ✅ Yes | No position、size info in API |
| Link Preview | ✅ Yes | Use unfurl.js |
| Mention | ✅ Yes | Only Date |
| Numbered List | ✅ Yes | |
| PDF | ✅ Yes | |
| Quote | ✅ Yes | |
| Synced block | ✅ Yes | |
| Table | ✅ Yes | |
| Table Of Contents | ❌ Missing | API Unsupported. |
| Template | ❌ Missing | Not planned. |
| To do | ✅ Yes | |
| Toggle blocks | ✅ Yes | |
| Video | ✅ Yes | Use [iframely](https://iframely.com/) |
| Block Type | Supported | Notes |
|------------------------|-----------|---------------------------------------------------------|
| Paragraph | ✅ Yes | |
| Headings | ✅ Yes | |
| Bookmark | ✅ Yes | Use unfurl.js |
| Bulleted List | ✅ Yes | |
| Callout | ✅ Yes | |
| Child Databases | ✅ Yes | Use [tanstack/table](https://tanstack.com/table/latest) |
| Child page | ✅ Yes | |
| Code | ✅ Yes | Use [shiki](https://shiki.style/) |
| Column list and column | ✅ Yes | |
| Embed | ✅ Yes | |
| Equation | ✅ Yes | Use [katex ](https://katex.org/) |
| File | ✅ Yes | |
| Image | ✅ Yes | No position、size info in API |
| Link Preview | ✅ Yes | Use unfurl.js |
| Mention | ✅ Yes | Only Date |
| Numbered List | ✅ Yes | |
| PDF | ✅ Yes | |
| Quote | ✅ Yes | |
| Synced block | ✅ Yes | |
| Table | ✅ Yes | |
| To do | ✅ Yes | |
| Toggle blocks | ✅ Yes | |
| Video | ✅ Yes | Use [iframely](https://iframely.com/) |
| Breadcrumb | ❌ Missing | Not planned |
| Template | ❌ Missing | Not planned. |
| Divider | ❌ Missing | API Unsupported. |
| Table Of Contents | ❌ Missing | API Unsupported. |



## Learn More
Expand Down
2 changes: 1 addition & 1 deletion app/(blog)/article/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default async function Page({ params }: { params: { slug: string[] } }) {
</PageHeader>
<Separator className="mb-2.5" />

<section className="w-full space-y-0.5">
<section className="w-full flex flex-col gap-y-0.5">
{blocks.map((block: any) => (
<Fragment key={block.id}>{renderBlock(block)}</Fragment>
))}
Expand Down
2 changes: 1 addition & 1 deletion app/notion/_components/callout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function CalloutRender({ block, className }: CalloutBlockProps) {
key={id}
className={cn(
className,
"inline-flex w-full items-center rounded-md border border-none bg-stone-100 p-4 dark:bg-stone-500"
"inline-flex w-full items-center rounded-md border border-none bg-stone-100 p-4 dark:bg-stone-500 my-0.5"
)}
>
{icon && <IconRender type={icon.type} emoji={icon.emoji} external={icon.external} file={icon.file} />}
Expand Down
2 changes: 1 addition & 1 deletion app/notion/_components/child-database.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function ChildDatabaseRenderer({ block, className }: ChildDatabaseB
return (
<Link key={id} href={slug} className={cn(className, "flex")}>
{icon && <IconRender type={icon.type} emoji={icon.emoji} external={icon.external} file={icon.file} />}
<div className="underline">
<div className="underline underline-offset-4 decoration-1">
<RichText title={title} />
</div>
<span className="sr-only">Inner database</span>
Expand Down
4 changes: 2 additions & 2 deletions app/notion/_components/db/cells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function titleCell(value: any) {
}
return (
<span>
<RichText title={value} extended="whitespace-pre-wrap" />
<RichText title={value} className="whitespace-pre-wrap" />
</span>
);
}
Expand Down Expand Up @@ -96,7 +96,7 @@ export function urlCell(value: any) {
return null;
}
return (
<div className="max-w-48 truncate underline">
<div className="max-w-48 truncate underline underline-offset-4 decoration-1">
<a href={value} target="_blank" rel="noreferrer noopener" className="text-gray-500">
{value}
</a>
Expand Down
6 changes: 3 additions & 3 deletions app/notion/_components/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export function FileRender({ block, className }: FileBlockProps) {
const fname = fileName(name, url);

return (
<div key={id} className={cn(className, "inline-flex w-full flex-wrap items-center")}>
<Icons.fileblock className="h-6 w-6 p-[0.8] text-gray-600" />
<div key={id} className={cn(className, "flex w-full items-center whitespace-nowrap overflow-hidden text-ellipsis")}>
<Icons.fileblock className="h-6 min-w-6 p-[0.8] text-gray-600" />
<a
href={url}
target="_blank"
rel="noreferrer noopener"
className="truncate whitespace-pre-wrap break-words font-normal"
className="truncate font-normal "
>
{caption && caption.length > 0 ? (
<figcaption className="px-1.5 text-sm font-normal text-slate-600">
Expand Down
78 changes: 78 additions & 0 deletions app/notion/_components/list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { cn } from "@/lib/utils";
import { renderBlock } from "../render";
import RichText, { ColorMap } from "../text";
import { IndentChildren } from "../render-helper";

export const numberListStyle = (level: number): string => {
const dep = ((level + 1) / 2) % 3;
if (dep == 0) {
return "list-roman";
}
if (dep == 1) {
return "list-decimal";
}
return "list-alpha";
};

export const bulletListStyle = (level: number): string => {
const dep = ((level + 1) / 2) % 2;
if (dep == 0) {
return "list-circle";
}
return "list-disc";
};

interface ListProps {
block: any;
level?: number;
className?: string | undefined;
}

export function ListRenderer({ block, level = 0, className }: ListProps) {
const { id, type } = block;
const value = block[type];

if (type == "bulleted_list") {
return (
<ul key={id} className={cn(className, bulletListStyle(level), "list-inside space-y-0.5")}>
{value?.children && value.children.map((child: any, index: number) => renderBlock(child, level + 1, index))}
</ul>
);
}

if (type == "numbered_list") {
return (
<ol key={id} className={cn(className, numberListStyle(level), "list-inside space-y-0.5")}>
{value?.children && value.children.map((child: any, index: number) => renderBlock(child, level + 1, index))}
</ol>
);
}
return null;
}

export function ListItemRenderer({ block, level = 0, className }: ListProps) {
const { id, type } = block;
const value = block?.[type];

const color = ColorMap.get(value?.color);
return (
<li key={id} className={cn(className, color, "pl-1 pt-1")}>
{value?.rich_text && <RichText title={value.rich_text} className="whitespace-pre-wrap" />}
{block.children && <IndentChildren cb={block.children} level={level+1}></IndentChildren>}
</li>
);
}

// function renderNestedList(blocks: any, level: number) {
// const { type } = blocks;
// const value = blocks[type];

// if (!value) return null;

// const isNumberedList = blocks.children[0].type === "numbered_list_item";
// if (isNumberedList) {
// // style not neccessary for grand-child lists as it will inherit from parent
// return <div>{blocks.children.map((block: any, index: number) => renderBlock(block, level, index))}</div>;
// }
// return <div>{blocks.children.map((block: any, index: number) => renderBlock(block, level, index))}</div>;
// }
10 changes: 8 additions & 2 deletions app/notion/_components/paragraph.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { cn } from "@/lib/utils";
import RichText, { ColorMap } from "../text";
import { IndentChildren } from "../render-helper";

interface ParagraphProps {
block: any;
level?: number;
className?: string | undefined;
}

export function ParagraphRender({ block, className }: ParagraphProps) {
export function ParagraphRender({ block, level = 0, className }: ParagraphProps) {
const {
id,
children,
paragraph: { rich_text, color },
} = block;

const style = ColorMap.get(color) || "";
return (
<div key={id} className={cn(className, style)}>
<p className="p-1">
<RichText title={rich_text} extended="whitespace-pre-wrap" />
<RichText title={rich_text} className="whitespace-pre-wrap" />
</p>
{children && (
<IndentChildren cb={children} level={level}></IndentChildren>
)}
</div>
);
}
8 changes: 4 additions & 4 deletions app/notion/_components/quote.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { cn } from "@/lib/utils";
import RichText from "../text";
// import { Icons } from "@/components/icons";
import { renderNestedList } from "../render";
import { renderBlock } from "../render";

interface QuoteBlockProps {
block: any;
Expand All @@ -12,13 +11,14 @@ interface QuoteBlockProps {
export function QuoteRender({ block, level, className }: QuoteBlockProps) {
const {
id,
children,
quote: { rich_text },
} = block;

return (
<blockquote key={id} className={cn(className, "whitespace-pre-wrap border-l-4 border-black py-1.5 pl-4")}>
<blockquote key={id} className={cn(className, "whitespace-pre-wrap border-l-[3px] border-black my-1.5 pl-4")}>
<RichText title={rich_text} />
{!!block.children && renderNestedList(block, level + 1)}
{children && children.map((child: any, index: number) => renderBlock(child, level, index))}
</blockquote>
);
}
2 changes: 1 addition & 1 deletion app/notion/_components/sub-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function SubPageRender({ block, className }: SubPageProps) {
<Icons.textFile className="h-6 w-6 p-[0.8] text-gray-400" />
<Link
href={slug}
className="truncate whitespace-pre-wrap break-words font-semibold underline decoration-slate-300"
className="truncate whitespace-pre-wrap break-words font-semibold underline underline-offset-4 decoration-1 decoration-slate-300"
>
{title}
</Link>
Expand Down
2 changes: 1 addition & 1 deletion app/notion/_components/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function TableRenderer({ block, className }: TableProps) {
return null;
}
return (
<Table key={id} className={cn(className, "border border-solid border-inherit")}>
<Table key={id} className={cn(className, "my-1 border border-solid border-inherit")}>
{has_column_header && renderHeader(block.children)}
{renderCell(has_column_header, has_row_header, block.children)}
</Table>
Expand Down
41 changes: 41 additions & 0 deletions app/notion/_components/to-do.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cn } from "@/lib/utils";
import RichText, { ColorMap } from "../text";
// import { renderBlock } from "../render";
import { IndentChildren } from "../render-helper";

interface TodoProps {
block: any;
level?: number;
className?: string | undefined;
}

export function TodoRender({ block, className, level = 0 }: TodoProps) {
const {
id,
children,
to_do: { rich_text, checked, color },
} = block;

const style = ColorMap.get(color) || "";
return (
<div key={id} className={cn(className, style)}>
<label htmlFor={id}>
<input
className="m-1 h-[1.1rem] w-[1.1rem] rounded-none border-8 align-middle"
type="checkbox"
id={id}
defaultChecked={checked}
/>
{checked ? (
<RichText title={rich_text} className="text-gray-500 line-through" />
) : (
<RichText title={rich_text} />
)}
</label>
{/**/}
{children && (
<IndentChildren cb={children} level={level+1}></IndentChildren>
)}
</div>
);
}
37 changes: 37 additions & 0 deletions app/notion/_components/toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cn } from "@/lib/utils";
import RichText, { ColorMap } from "../text";
// import { renderBlock } from "../render";
import { IndentChildren } from "../render-helper";
import React from "react";
import { renderBlock } from "../render";

interface ToggleProps {
block: any;
level?: number;
className?: string | undefined;
}

export function ToggleRender({ block, className, level = 0 }: ToggleProps) {
const {
id,
children,
toggle: { rich_text, color },
} = block;

const style = ColorMap.get(color) || "";
return (
<div key={id} className={cn(className, style)}>
<details key={id} className="my-1">
<summary>
<RichText title={rich_text} />
</summary>

<div className="ml-2">
{children?.map((child: any) => (
<React.Fragment key={child.id}>{renderBlock(child, level + 1)}</React.Fragment>
))}
</div>
</details>
</div>
);
}
17 changes: 17 additions & 0 deletions app/notion/render-helper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { renderBlock } from "./render";


export function IndentChildren({ cb, level = 0 }: { cb: any; level: number }) {
if (!cb) {
return null;
}

return (
<div className="flex w-full max-w-full">
<div className="h-auto w-6"></div>
<div className="flex w-full max-w-full flex-col gap-y-0.5">
{cb && cb.map((child: any, index: number) => renderBlock(child, level, index))}
</div>
</div>
);
}
Loading

0 comments on commit 5999897

Please sign in to comment.