Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ Most common block types are supported. We happily accept pull requests to add su
| Checkbox | ❌ Missing | Supported by [react-notion-x](https://github.com/NotionX/react-notion-x) |
| Table Of Contents | ❌ Missing | Supported by [react-notion-x](https://github.com/NotionX/react-notion-x) |

## Unsupported Blocks

If a Notion page contains a block type that `react-notion` cannot render, `NotionRenderer` now renders a visible warning instead of silently outputting an empty placeholder. This makes unsupported databases, checkboxes, or future Notion block types easier to diagnose.

```jsx
<NotionRenderer
blockMap={blockMap}
renderUnsupportedBlock={blockType => (
<p>This Notion page contains an unsupported block: {blockType}</p>
)}
/>
```

To keep the previous silent behavior, pass `showUnsupportedBlockError={false}`.

## Block Type Specific Caveats

When using a code block in your Notion page, `NotionRenderer` will use `prismjs` to detect the language of the code block.
Expand Down
59 changes: 51 additions & 8 deletions src/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
MapPageUrl,
MapImageUrl,
CustomBlockComponents,
BlockValueProp,
CustomDecoratorComponents,
CustomDecoratorComponentProps
CustomDecoratorComponentProps,
UnsupportedBlockRenderer
} from "./types";
import Asset from "./components/asset";
import Code from "./components/code";
Expand Down Expand Up @@ -92,6 +92,8 @@ interface Block {

fullPage?: boolean;
hideHeader?: boolean;
showUnsupportedBlockError?: boolean;
renderUnsupportedBlock?: UnsupportedBlockRenderer;
customBlockComponents?: CustomBlockComponents;
customDecoratorComponents?: CustomDecoratorComponents;
}
Expand All @@ -106,11 +108,34 @@ export const Block: React.FC<Block> = props => {
blockMap,
mapPageUrl,
mapImageUrl,
showUnsupportedBlockError = true,
renderUnsupportedBlock,
customBlockComponents,
customDecoratorComponents
} = props;
const blockValue = block?.value;

const renderUnsupportedBlockError = (blockType: string) => {
if (renderUnsupportedBlock) {
return renderUnsupportedBlock(blockType, block);
}

if (!showUnsupportedBlockError) {
return <div />;
}

return (
<div className="notion-unsupported-block" role="alert">
<strong>Unsupported Notion block: {blockType}</strong>
<span>
{" "}
This block type cannot be rendered by react-notion. Remove it from the
Notion page or pass renderUnsupportedBlock to customize this message.
</span>
</div>
);
};

const renderComponent = () => {
const renderChildText = createRenderChildText(customDecoratorComponents);

Expand Down Expand Up @@ -360,9 +385,20 @@ export const Block: React.FC<Block> = props => {
</blockquote>
);
case "collection_view":
if (!block) return null;
if (!block.collection) {
return renderUnsupportedBlockError("collection_view");
}

const collectionView = block.collection.types[0];

const collectionView = block?.collection?.types[0];
if (
!collectionView ||
!["table", "gallery"].includes(collectionView.type)
) {
return renderUnsupportedBlockError(
`collection_view:${collectionView?.type || "unknown"}`
);
}

return (
<div>
Expand Down Expand Up @@ -516,10 +552,15 @@ export const Block: React.FC<Block> = props => {
</details>
);
default:
const unsupportedType = String(
(blockValue as { type?: string } | undefined)?.type || "unknown"
);

if (process.env.NODE_ENV !== "production") {
console.log("Unsupported type " + block?.value?.type);
console.warn("Unsupported Notion block type " + unsupportedType);
}
return <div />;

return renderUnsupportedBlockError(unsupportedType);
}
return null;
};
Expand All @@ -531,12 +572,14 @@ export const Block: React.FC<Block> = props => {
// Do not use custom component for base page block
level !== 0
) {
const CustomComponent = customBlockComponents[blockValue?.type]!;
const CustomComponent = customBlockComponents[
blockValue?.type
]! as React.FC<any>;
return (
<CustomComponent
renderComponent={renderComponent}
blockMap={blockMap}
blockValue={blockValue as BlockValueProp<typeof blockValue.type>}
blockValue={blockValue}
level={level}
>
{children}
Expand Down
5 changes: 4 additions & 1 deletion src/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
MapPageUrl,
MapImageUrl,
CustomBlockComponents,
CustomDecoratorComponents
CustomDecoratorComponents,
UnsupportedBlockRenderer
} from "./types";
import { Block } from "./block";
import { defaultMapImageUrl, defaultMapPageUrl } from "./utils";
Expand All @@ -15,6 +16,8 @@ export interface NotionRendererProps {
hideHeader?: boolean;
mapPageUrl?: MapPageUrl;
mapImageUrl?: MapImageUrl;
showUnsupportedBlockError?: boolean;
renderUnsupportedBlock?: UnsupportedBlockRenderer;

currentId?: string;
level?: number;
Expand Down
10 changes: 10 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -681,3 +681,13 @@ img.notion-nav-icon {
margin: 0 2px;
color: rgba(55, 53, 47, 0.4);
}
.notion-unsupported-block {
margin: 0.5rem 0;
padding: 0.75rem 1rem;
border-left: 3px solid #d9822b;
border-radius: 3px;
background: #fff4e5;
color: #7a4b00;
font-size: 0.95rem;
line-height: 1.4;
}
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ export interface LoadPageChunkData {

export type MapPageUrl = (pageId: string) => string;
export type MapImageUrl = (image: string, block?: BlockType) => string;
export type UnsupportedBlockRenderer = (
blockType: string,
block: BlockType
) => JSX.Element | null;

export type BlockValueProp<T> = Extract<BlockValueType, { type: T }>;

Expand Down
59 changes: 59 additions & 0 deletions src/unsupported-block.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { NotionRenderer } from "./renderer";
import { BlockMapType } from "./types";

const createBlockMap = (type: string): BlockMapType =>
(({
root: {
role: "reader",
value: {
id: "root",
version: 1,
type,
created_time: 0,
last_edited_time: 0,
parent_id: "",
parent_table: "space",
alive: true,
created_by_table: "notion_user",
created_by_id: "user",
last_edited_by_table: "notion_user",
last_edited_by_id: "user"
}
}
} as unknown) as BlockMapType);

describe("unsupported Notion blocks", () => {
it("renders a visible error for unsupported block types", () => {
const html = renderToStaticMarkup(
<NotionRenderer blockMap={createBlockMap("collection_view_page")} />
);

expect(html).toContain("Unsupported Notion block: collection_view_page");
});

it("allows unsupported block rendering to be customized", () => {
const html = renderToStaticMarkup(
<NotionRenderer
blockMap={createBlockMap("unsupported_checkbox")}
renderUnsupportedBlock={blockType => (
<p>Custom unsupported block: {blockType}</p>
)}
/>
);

expect(html).toContain("Custom unsupported block: unsupported_checkbox");
});

it("can keep the previous silent placeholder behavior", () => {
const html = renderToStaticMarkup(
<NotionRenderer
blockMap={createBlockMap("collection_view_page")}
showUnsupportedBlockError={false}
/>
);

expect(html).not.toContain("Unsupported Notion block");
});
});