Skip to content

Commit 269c6ad

Browse files
feat: bulk import of improvements made for notion2site.com
1 parent f561272 commit 269c6ad

13 files changed

+453
-81
lines changed

example/tsconfig.json

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "es5",
4-
"lib": [
5-
"dom",
6-
"dom.iterable",
7-
"esnext"
8-
],
4+
"lib": ["dom", "dom.iterable", "esnext"],
95
"allowJs": true,
106
"skipLibCheck": true,
117
"strict": false,
@@ -18,12 +14,6 @@
1814
"isolatedModules": true,
1915
"jsx": "preserve"
2016
},
21-
"exclude": [
22-
"node_modules"
23-
],
24-
"include": [
25-
"next-env.d.ts",
26-
"**/*.ts",
27-
"**/*.tsx"
28-
]
17+
"exclude": ["node_modules"],
18+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
2919
}

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
"typescript": "^3.8.3"
5959
},
6060
"dependencies": {
61-
"prismjs": "^1.20.0"
61+
"medium-zoom": "^1.0.5",
62+
"prismjs": "^1.20.0",
63+
"react-modal": "^3.11.2"
6264
}
6365
}

src/block.tsx

+61-43
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
DecorationType,
44
BlockType,
55
ContentValueType,
6-
BlockMapType
6+
BlockMapType,
7+
MapPageUrl
78
} from "./types";
89
import Asset from "./components/asset";
910
import Code from "./components/code";
1011
import PageIcon from "./components/page-icon";
12+
import PageHeader from "./components/page-header";
1113
import {
1214
classNames,
1315
getTextContent,
@@ -55,20 +57,28 @@ export const renderChildText = (properties: DecorationType[]) => {
5557
});
5658
};
5759

58-
export type MapPageUrl = (pageId: string) => string;
59-
6060
interface Block {
6161
block: BlockType;
6262
level: number;
6363
blockMap: BlockMapType;
64+
mapPageUrl: MapPageUrl;
6465

6566
fullPage?: boolean;
66-
mapPageUrl?: MapPageUrl;
67+
zoom?: any;
6768
}
6869

6970
export const Block: React.FC<Block> = props => {
70-
const { block, children, level, fullPage, blockMap } = props;
71+
const {
72+
block,
73+
children,
74+
level,
75+
fullPage,
76+
blockMap,
77+
zoom,
78+
mapPageUrl
79+
} = props;
7180
const blockValue = block?.value;
81+
7282
switch (blockValue?.type) {
7383
case "page":
7484
if (level === 0) {
@@ -88,51 +98,58 @@ export const Block: React.FC<Block> = props => {
8898
const coverPosition = (1 - (page_cover_position || 0.5)) * 100;
8999

90100
return (
91-
<div className="notion">
92-
{page_cover && (
93-
<img
94-
src={toNotionImageUrl(page_cover)}
95-
alt={getTextContent(blockValue.properties.title)}
96-
className="notion-page-cover"
97-
style={{
98-
objectPosition: `center ${coverPosition}%`
99-
}}
100-
/>
101-
)}
102-
<div
103-
className={classNames(
104-
"notion-page",
105-
!page_cover && "notion-page-offset",
106-
page_full_width && "notion-full-width",
107-
page_small_text && "notion-small-text"
108-
)}
109-
>
110-
{page_icon && (
111-
<PageIcon
112-
className={
113-
page_cover ? "notion-page-icon-offset" : undefined
114-
}
115-
block={block}
116-
big
117-
/>
118-
)}
119-
<div className="notion-title">
120-
{renderChildText(blockValue.properties.title)}
101+
<div className="notion notion-app">
102+
<div className="notion-cursor-listener">
103+
<div className="notion-frame">
104+
<PageHeader blockMap={blockMap} mapPageUrl={mapPageUrl} />
105+
106+
<div className="notion-scroller">
107+
{page_cover && (
108+
<img
109+
src={toNotionImageUrl(page_cover)}
110+
alt={getTextContent(blockValue.properties.title)}
111+
className="notion-page-cover"
112+
style={{
113+
objectPosition: `center ${coverPosition}%`
114+
}}
115+
/>
116+
)}
117+
<main
118+
className={classNames(
119+
"notion-page",
120+
!page_cover && "notion-page-offset",
121+
page_full_width && "notion-full-width",
122+
page_small_text && "notion-small-text"
123+
)}
124+
>
125+
{page_icon && (
126+
<PageIcon
127+
className={
128+
page_cover ? "notion-page-icon-offset" : undefined
129+
}
130+
block={block}
131+
big
132+
/>
133+
)}
134+
135+
<div className="notion-title">
136+
{renderChildText(blockValue.properties.title)}
137+
</div>
138+
139+
{children}
140+
</main>
141+
</div>
121142
</div>
122-
{children}
123143
</div>
124144
</div>
125145
);
126146
} else {
127-
return <div className="notion">{children}</div>;
147+
return <main className="notion">{children}</main>;
128148
}
129149
} else {
130150
if (!blockValue.properties) return null;
131151
return (
132-
<a
133-
className="notion-page-link"
134-
href={props.mapPageUrl?.(blockValue.id) || `/${blockValue.id}`}
135-
>
152+
<a className="notion-page-link" href={mapPageUrl(blockValue.id)}>
136153
{blockValue.format && (
137154
<div className="notion-page-icon">
138155
<PageIcon block={block} />
@@ -169,7 +186,7 @@ export const Block: React.FC<Block> = props => {
169186
return <hr className="notion-hr" />;
170187
case "text":
171188
if (!blockValue.properties) {
172-
return <div className="notion-blank" />;
189+
return <div className="notion-blank">&nbsp;</div>;
173190
}
174191
const blockColor = blockValue.format?.block_color;
175192
return (
@@ -226,7 +243,8 @@ export const Block: React.FC<Block> = props => {
226243
className="notion-asset-wrapper"
227244
style={{ width: value.format.block_width }}
228245
>
229-
<Asset block={block} />
246+
<Asset block={block} zoom={zoom} />
247+
230248
{value.properties.caption && (
231249
<figcaption className="notion-image-caption">
232250
{renderChildText(value.properties.caption)}

src/components/asset.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { toNotionImageUrl } from "../utils";
44

55
const types = ["video", "image", "embed"];
66

7-
const Asset: React.FC<{ block: BlockType }> = ({ block }) => {
7+
const Asset: React.FC<{ block: BlockType; zoom?: any }> = ({ block, zoom }) => {
8+
const zoomRef = React.useRef(zoom ? zoom.clone() : null);
89
const value = block.value as ContentValueType;
910
const type = block.value.type;
1011

@@ -37,8 +38,15 @@ const Asset: React.FC<{ block: BlockType }> = ({ block }) => {
3738

3839
const src = toNotionImageUrl(value.properties.source[0][0]);
3940

41+
function attachZoom(image: any) {
42+
if (zoomRef.current) {
43+
(zoomRef.current as any).attach(image);
44+
}
45+
}
46+
4047
if (type === "image") {
4148
const caption = value.properties.caption?.[0][0];
49+
4250
if (block_aspect_ratio) {
4351
return (
4452
<div
@@ -47,11 +55,16 @@ const Asset: React.FC<{ block: BlockType }> = ({ block }) => {
4755
position: "relative"
4856
}}
4957
>
50-
<img className="notion-image-inset" alt={caption} src={src} />
58+
<img
59+
className="notion-image-inset"
60+
alt={caption || "notion image"}
61+
src={src}
62+
ref={attachZoom}
63+
/>
5164
</div>
5265
);
5366
} else {
54-
return <img alt={caption} src={src} />;
67+
return <img alt={caption} src={src} ref={attachZoom} />;
5568
}
5669
}
5770

src/components/code.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const Code: React.FC<{ code: string; language: string }> = ({
66
code,
77
language = "javascript"
88
}) => {
9-
const prismLanguage =
10-
languages[language.toLowerCase()] || languages.javascript;
9+
const languageL = language.toLowerCase();
10+
const prismLanguage = languages[languageL] || languages.javascript;
1111

1212
const langClass = `language-${language.toLowerCase()}`;
1313

src/components/page-header.tsx

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as React from "react";
2+
3+
import { BlockMapType, MapPageUrl } from "../types";
4+
import PageIcon from "./page-icon";
5+
6+
interface PageHeaderProps {
7+
blockMap: BlockMapType;
8+
mapPageUrl: MapPageUrl;
9+
}
10+
11+
const PageHeader: React.FC<PageHeaderProps> = ({ blockMap, mapPageUrl }) => {
12+
const blockIds = Object.keys(blockMap);
13+
const activePageId = blockIds[0];
14+
15+
if (!activePageId) {
16+
return null;
17+
}
18+
19+
const breadcrumbs = [];
20+
let currentPageId = activePageId;
21+
22+
do {
23+
const block = blockMap[currentPageId];
24+
if (!block || !block.value) {
25+
break;
26+
}
27+
28+
const title = block.value.properties?.title[0][0];
29+
const icon = (block.value as any).format?.page_icon;
30+
31+
if (!(title || icon)) {
32+
break;
33+
}
34+
35+
breadcrumbs.push({
36+
block,
37+
active: currentPageId === activePageId,
38+
pageId: currentPageId,
39+
title,
40+
icon
41+
});
42+
43+
const parentId = block.value.parent_id;
44+
45+
if (!parentId) {
46+
break;
47+
}
48+
49+
currentPageId = parentId;
50+
} while (true);
51+
52+
breadcrumbs.reverse();
53+
54+
return (
55+
<header className="notion-page-header">
56+
<div className="notion-nav-header">
57+
<div className="notion-nav-breadcrumbs">
58+
{breadcrumbs.map((breadcrumb, index) => (
59+
<React.Fragment key={breadcrumb.pageId}>
60+
<a
61+
className={`notion-nav-breadcrumb ${
62+
breadcrumb.active ? "notion-nav-breadcrumb-active" : ""
63+
}`}
64+
href={
65+
breadcrumb.active ? undefined : mapPageUrl(breadcrumb.pageId)
66+
}
67+
>
68+
{breadcrumb.icon && (
69+
<PageIcon
70+
className="notion-nav-icon"
71+
block={breadcrumb.block}
72+
/>
73+
)}
74+
75+
{breadcrumb.title && (
76+
<span className="notion-nav-title">{breadcrumb.title}</span>
77+
)}
78+
</a>
79+
80+
{index < breadcrumbs.length - 1 && (
81+
<span className="notion-nav-spacer">/</span>
82+
)}
83+
</React.Fragment>
84+
))}
85+
</div>
86+
</div>
87+
</header>
88+
);
89+
};
90+
91+
export default PageHeader;

src/components/page-icon.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@ const PageIcon: React.FC<AssetProps> = ({ block, className, big }) => {
2323
if (!isIconBlock(block.value)) {
2424
return null;
2525
}
26-
const icon = block.value.format.page_icon;
26+
const icon = block.value.format?.page_icon;
2727
const title = block.value.properties?.title;
2828

2929
if (icon?.includes("http")) {
30+
const url = toNotionImageUrl(icon);
31+
3032
return (
3133
<img
3234
className={classNames(
3335
className,
3436
big ? "notion-page-icon-cover" : "notion-page-icon"
3537
)}
36-
src={toNotionImageUrl(icon)}
38+
src={url}
3739
alt={title ? getTextContent(title) : "Icon"}
3840
/>
3941
);
@@ -44,7 +46,7 @@ const PageIcon: React.FC<AssetProps> = ({ block, className, big }) => {
4446
className,
4547
big ? "notion-page-icon-cover" : "notion-page-icon"
4648
)}
47-
role="image"
49+
role="img"
4850
aria-label={icon}
4951
>
5052
{icon}

src/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { NotionRenderer } from "./renderer";
22
export * from "./types";
3+
export * from "./utils";

0 commit comments

Comments
 (0)