Skip to content

Commit a8d7b3a

Browse files
committed
Add support for custom decorator components
- Rename customComponents to customBlockComponents
1 parent 356f17a commit a8d7b3a

File tree

5 files changed

+101
-47
lines changed

5 files changed

+101
-47
lines changed

example/pages/[pageId].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const NotionPage = ({ blockMap }) => {
4444
<NotionRenderer
4545
blockMap={blockMap}
4646
fullPage
47-
customComponents={{
47+
customBlockComponents={{
4848
page: ({ blockValue, renderComponent }) => (
4949
<Link href={`/${blockValue.id}`}>{renderComponent()}</Link>
5050
)

example/pages/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const Home = ({ blockMap }) => (
2929
</Head>
3030
<NotionRenderer
3131
blockMap={blockMap}
32-
customComponents={{
32+
customBlockComponents={{
3333
page: ({ blockValue, renderComponent }) => (
3434
<Link href={`/${blockValue.id}`}>{renderComponent()}</Link>
3535
)

src/block.tsx

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,79 @@ import {
66
BlockMapType,
77
MapPageUrl,
88
MapImageUrl,
9-
CustomComponents,
10-
BlockValueProp
9+
CustomBlockComponents,
10+
BlockValueProp,
11+
CustomDecoratorComponents,
12+
CustomDecoratorComponentProps
1113
} from "./types";
1214
import Asset from "./components/asset";
1315
import Code from "./components/code";
1416
import PageIcon from "./components/page-icon";
1517
import PageHeader from "./components/page-header";
1618
import { classNames, getTextContent, getListNumber } from "./utils";
1719

18-
export const renderChildText = (properties: DecorationType[]) => {
20+
export const createRenderChildText = (
21+
customDecoratorComponents?: CustomDecoratorComponents
22+
) => (properties: DecorationType[]) => {
1923
return properties?.map(([text, decorations], i) => {
2024
if (!decorations) {
2125
return <React.Fragment key={i}>{text}</React.Fragment>;
2226
}
2327

2428
return decorations.reduceRight((element, decorator) => {
25-
switch (decorator[0]) {
26-
case "h":
27-
return (
28-
<span key={i} className={`notion-${decorator[1]}`}>
29-
{element}
30-
</span>
31-
);
32-
case "c":
33-
return (
34-
<code key={i} className="notion-inline-code">
35-
{element}
36-
</code>
37-
);
38-
case "b":
39-
return <b key={i}>{element}</b>;
40-
case "i":
41-
return <em key={i}>{element}</em>;
42-
case "s":
43-
return <s key={i}>{element}</s>;
44-
case "a":
45-
return (
46-
<a className="notion-link" href={decorator[1]} key={i}>
47-
{element}
48-
</a>
49-
);
29+
const renderText = () => {
30+
switch (decorator[0]) {
31+
case "h":
32+
return (
33+
<span key={i} className={`notion-${decorator[1]}`}>
34+
{element}
35+
</span>
36+
);
37+
case "c":
38+
return (
39+
<code key={i} className="notion-inline-code">
40+
{element}
41+
</code>
42+
);
43+
case "b":
44+
return <b key={i}>{element}</b>;
45+
case "i":
46+
return <em key={i}>{element}</em>;
47+
case "s":
48+
return <s key={i}>{element}</s>;
49+
case "a":
50+
return (
51+
<a className="notion-link" href={decorator[1]} key={i}>
52+
{element}
53+
</a>
54+
);
55+
56+
default:
57+
return <React.Fragment key={i}>{element}</React.Fragment>;
58+
}
59+
};
60+
61+
const CustomComponent = customDecoratorComponents?.[decorator[0]];
62+
63+
if (CustomComponent) {
64+
const props = (decorator[1]
65+
? {
66+
decoratorValue: decorator[1]
67+
}
68+
: {}) as CustomDecoratorComponentProps<typeof decorator[0]>;
5069

51-
default:
52-
return <React.Fragment key={i}>{element}</React.Fragment>;
70+
return (
71+
<CustomComponent
72+
key={i}
73+
{...(props as any)}
74+
renderComponent={renderText}
75+
>
76+
{text}
77+
</CustomComponent>
78+
);
5379
}
80+
81+
return renderText();
5482
}, <>{text}</>);
5583
});
5684
};
@@ -64,7 +92,8 @@ interface Block {
6492

6593
fullPage?: boolean;
6694
hideHeader?: boolean;
67-
customComponents?: CustomComponents;
95+
customBlockComponents?: CustomBlockComponents;
96+
customDecoratorComponents?: CustomDecoratorComponents;
6897
}
6998

7099
export const Block: React.FC<Block> = props => {
@@ -77,11 +106,14 @@ export const Block: React.FC<Block> = props => {
77106
blockMap,
78107
mapPageUrl,
79108
mapImageUrl,
80-
customComponents
109+
customBlockComponents,
110+
customDecoratorComponents
81111
} = props;
82112
const blockValue = block?.value;
83113

84114
const renderComponent = () => {
115+
const renderChildText = createRenderChildText(customDecoratorComponents);
116+
85117
switch (blockValue?.type) {
86118
case "page":
87119
if (level === 0) {
@@ -468,8 +500,13 @@ export const Block: React.FC<Block> = props => {
468500
};
469501

470502
// render a custom component first if passed.
471-
if (customComponents && customComponents[blockValue?.type] && level !== 0) {
472-
const CustomComponent = customComponents[blockValue?.type]!;
503+
if (
504+
customBlockComponents &&
505+
customBlockComponents[blockValue?.type] &&
506+
// Do not use custom component for base page block
507+
level !== 0
508+
) {
509+
const CustomComponent = customBlockComponents[blockValue?.type]!;
473510
return (
474511
<CustomComponent
475512
renderComponent={renderComponent}

src/renderer.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
BlockMapType,
44
MapPageUrl,
55
MapImageUrl,
6-
CustomComponents
6+
CustomBlockComponents,
7+
CustomDecoratorComponents
78
} from "./types";
89
import { Block } from "./block";
910
import { defaultMapImageUrl, defaultMapPageUrl } from "./utils";
@@ -17,7 +18,8 @@ export interface NotionRendererProps {
1718

1819
currentId?: string;
1920
level?: number;
20-
customComponents?: CustomComponents;
21+
customBlockComponents?: CustomBlockComponents;
22+
customDecoratorComponents?: CustomDecoratorComponents;
2123
}
2224

2325
export const NotionRenderer: React.FC<NotionRendererProps> = ({

src/types.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -344,16 +344,31 @@ export type MapImageUrl = (image: string, block?: BlockType) => string;
344344

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

347-
export interface CustomComponentProps<T extends BlockValueTypeKeys> {
347+
export interface CustomBlockComponentProps<T extends BlockValueTypeKeys> {
348348
renderComponent: () => JSX.Element | null;
349-
blockValue: T extends BlockValueType
350-
? Extract<BlockValueType, { type: T }>
351-
: BaseValueType;
349+
blockValue: T extends BlockValueType ? BlockValueProp<T> : BaseValueType;
352350
}
353351

354-
export type CustomComponent<T extends BlockValueTypeKeys> = FC<
355-
CustomComponentProps<T>
356-
>;
357-
export type CustomComponents = {
358-
[K in BlockValueTypeKeys]?: CustomComponent<K>;
352+
export type CustomBlockComponents = {
353+
[K in BlockValueTypeKeys]?: FC<CustomBlockComponentProps<K>>;
354+
};
355+
356+
type SubDecorationSymbol = SubDecorationType[0];
357+
type SubDecorationValue<T extends SubDecorationSymbol> = Extract<
358+
SubDecorationType,
359+
[T, any]
360+
>[1];
361+
362+
export type CustomDecoratorComponentProps<
363+
T extends SubDecorationSymbol
364+
> = (SubDecorationValue<T> extends never
365+
? {}
366+
: {
367+
decoratorValue: SubDecorationValue<T>;
368+
}) & {
369+
renderComponent: () => JSX.Element | null;
370+
};
371+
372+
export type CustomDecoratorComponents = {
373+
[K in SubDecorationSymbol]?: FC<CustomDecoratorComponentProps<K>>;
359374
};

0 commit comments

Comments
 (0)