Skip to content

Commit beaf6f0

Browse files
committed
Add first working version
1 parent febcb43 commit beaf6f0

13 files changed

+7101
-16
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Tobias Lins
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

package.json

+45-16
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,57 @@
11
{
2-
"name": "react-notion",
3-
"version": "0.0.1",
4-
"description": "A react renderer that renders Notion.so data",
5-
"main": "index.js",
6-
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
2+
"version": "0.1.0",
3+
"license": "MIT",
4+
"main": "dist/index.js",
5+
"typings": "dist/index.d.ts",
6+
"files": [
7+
"dist",
8+
"src"
9+
],
10+
"engines": {
11+
"node": ">=10"
812
},
9-
"repository": {
10-
"type": "git",
11-
"url": "git+https://github.com/splitbee/react-notion.git"
13+
"scripts": {
14+
"start": "tsdx watch",
15+
"build": "tsdx build",
16+
"test": "tsdx test --passWithNoTests",
17+
"lint": "tsdx lint",
18+
"prepare": "tsdx build"
1219
},
13-
"keywords": [
14-
"notion",
15-
"react",
16-
"renderer"
17-
],
1820
"author": {
1921
"name": "Tobias Lins",
2022
"email": "[email protected]",
2123
"url": "https://tobi.sh"
2224
},
23-
"license": "MIT",
25+
"peerDependencies": {
26+
"prismjs": "^1.20.0",
27+
"react": ">=16"
28+
},
29+
"husky": {
30+
"hooks": {
31+
"pre-commit": "tsdx lint"
32+
}
33+
},
34+
"prettier": {},
2435
"bugs": {
2536
"url": "https://github.com/splitbee/react-notion/issues"
2637
},
27-
"homepage": "https://github.com/splitbee/react-notion#readme"
38+
"homepage": "https://github.com/splitbee/react-notion#readme",
39+
"repository": {
40+
"type": "git",
41+
"url": "git+https://github.com/splitbee/react-notion.git"
42+
},
43+
"name": "react-notion",
44+
"module": "dist/react-notion.esm.js",
45+
"devDependencies": {
46+
"@types/prismjs": "^1.16.0",
47+
"@types/react": "^16.9.34",
48+
"@types/react-dom": "^16.9.6",
49+
"husky": "^4.2.5",
50+
"react": "^16.13.1",
51+
"react-dom": "^16.13.1",
52+
"rollup-plugin-copy": "^3.3.0",
53+
"tsdx": "^0.13.2",
54+
"tslib": "^1.11.1",
55+
"typescript": "^3.8.3"
56+
}
2857
}

src/block.tsx

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import * as React from "react";
2+
import { LoadPageChunkData, DecorationType } from "./types";
3+
import Asset from "./components/asset";
4+
import Code from "./components/code";
5+
6+
// import './styles.css';
7+
8+
export const applyDecorator = (properties: DecorationType[]) => {
9+
return properties.map((item, index) => {
10+
let newItem: JSX.Element = <>{item[0]}</>;
11+
if (item.length !== 1) {
12+
item[1].forEach(item => {
13+
switch (item[0]) {
14+
case "b":
15+
newItem = <b key={index}>{newItem}</b>;
16+
break;
17+
case "i":
18+
newItem = <em key={index}>{newItem}</em>;
19+
break;
20+
case "s":
21+
newItem = <s key={index}>{newItem}</s>;
22+
break;
23+
case "a":
24+
newItem = (
25+
<a className="notion-link" href={item[1]} key={index}>
26+
{newItem}
27+
</a>
28+
);
29+
break;
30+
case "c":
31+
newItem = (
32+
<code className="notion-inline-code" key={index}>
33+
{newItem}
34+
</code>
35+
);
36+
break;
37+
case "h":
38+
newItem = <span className={item[1]}>{newItem}</span>;
39+
}
40+
});
41+
}
42+
return newItem;
43+
});
44+
};
45+
46+
interface BlockRenderer {
47+
block: LoadPageChunkData["recordMap"]["block"][""];
48+
level: number;
49+
}
50+
51+
export const BlockRenderer: React.FC<BlockRenderer> = props => {
52+
const { block } = props;
53+
switch (block.value.type) {
54+
case "page":
55+
return null;
56+
case "header":
57+
if (!block.value.properties) return null;
58+
return (
59+
<h1 className="notion-h1">
60+
<>{applyDecorator(block.value.properties.title)}</>
61+
</h1>
62+
);
63+
case "sub_header":
64+
if (!block.value.properties) return null;
65+
return (
66+
<h2 className="notion-h2">
67+
<>{applyDecorator(block.value.properties.title)}</>
68+
</h2>
69+
);
70+
case "sub_sub_header":
71+
if (!block.value.properties) return null;
72+
return (
73+
<h3 className="notion-h3">
74+
<>{applyDecorator(block.value.properties.title)}</>
75+
</h3>
76+
);
77+
case "column_list":
78+
return null;
79+
case "quote":
80+
if (!block.value.properties) return null;
81+
return (
82+
<blockquote className="notion-quote">
83+
<>{applyDecorator(block.value.properties.title)}</>
84+
</blockquote>
85+
);
86+
case "column":
87+
return null;
88+
case "divider":
89+
return <hr />;
90+
case "text":
91+
if (!block.value.properties) {
92+
return <p style={{ height: "1rem" }}> </p>;
93+
}
94+
return (
95+
<p className={`notion-text`}>
96+
<>{applyDecorator(block.value.properties.title)}</>
97+
</p>
98+
);
99+
case "bulleted_list":
100+
case "numbered_list":
101+
if (!block.value.properties) return null;
102+
return (
103+
<li className={``}>
104+
<>{applyDecorator(block.value.properties.title)}</>
105+
</li>
106+
);
107+
case "image":
108+
case "embed":
109+
case "video":
110+
return (
111+
<div className="notion-asset-wrapper">
112+
<Asset block={block} />
113+
</div>
114+
);
115+
case "code": {
116+
if (block.value.properties.title) {
117+
const content = block.value.properties.title[0][0];
118+
const language = block.value.properties.language[0][0];
119+
return (
120+
<Code key={block.value.id} language={language || ""} code={content} />
121+
);
122+
}
123+
break;
124+
}
125+
default:
126+
console.log("Unsupported type " + block.value.type);
127+
return <div />;
128+
}
129+
return null;
130+
};
131+
132+
interface ChildRendererProps {
133+
blockMap: LoadPageChunkData["recordMap"]["block"];
134+
level: number;
135+
ids: string[];
136+
}
137+
138+
export const ChildRenderer: React.FC<ChildRendererProps> = props => {
139+
const { ids, blockMap } = props;
140+
141+
let idArray = [];
142+
let bulletArray = [];
143+
let orderedArray = [];
144+
145+
for (let i = 0; i < ids.length; i++) {
146+
const currentId = ids[i];
147+
if (!(currentId in blockMap)) continue;
148+
const currentBlock = blockMap[currentId];
149+
if (currentBlock.value.type === "bulleted_list") {
150+
bulletArray.push(
151+
<NotionRenderer
152+
level={props.level + 1}
153+
currentID={ids[i]}
154+
blockMap={blockMap}
155+
/>
156+
);
157+
} else if (currentBlock.value.type === "numbered_list") {
158+
orderedArray.push(
159+
<NotionRenderer
160+
level={props.level + 1}
161+
currentID={ids[i]}
162+
blockMap={blockMap}
163+
/>
164+
);
165+
} else if (currentBlock.value.type === "column_list") {
166+
idArray.push(
167+
<div className="notion-row">
168+
<NotionRenderer
169+
level={props.level + 1}
170+
currentID={currentId}
171+
blockMap={blockMap}
172+
/>
173+
</div>
174+
);
175+
} else if (currentBlock.value.type === "column") {
176+
const spacerWith = 46;
177+
const spacerTotalWith = (idArray.length - 1) * spacerWith;
178+
const width = `calc((100% - ${spacerTotalWith}px) * ${currentBlock.value.format.column_ratio})`;
179+
idArray.push(
180+
<>
181+
<div className="notion-column" style={{ width }}>
182+
<NotionRenderer
183+
level={props.level + 1}
184+
currentID={ids[i]}
185+
blockMap={blockMap}
186+
/>
187+
</div>
188+
{idArray.length !== ids.length - 1 && <div style={{ width: 46 }} />}
189+
</>
190+
);
191+
} else {
192+
if (bulletArray.length > 0) {
193+
idArray.push(
194+
<ul className="notion-list notion-list-disc">{bulletArray}</ul>
195+
);
196+
bulletArray = [];
197+
}
198+
if (orderedArray.length > 0) {
199+
idArray.push(
200+
<ol className="notion-list notion-list-numbered" key={i}>
201+
{orderedArray}
202+
</ol>
203+
);
204+
orderedArray = [];
205+
}
206+
207+
idArray.push(
208+
<div className="notion-block" key={i}>
209+
<NotionRenderer
210+
level={props.level + 1}
211+
currentID={ids[i]}
212+
blockMap={blockMap}
213+
/>
214+
</div>
215+
);
216+
}
217+
}
218+
219+
if (bulletArray.length > 0) {
220+
idArray.push(
221+
<ul className="notion-list notion-list-disc" key={idArray.length}>
222+
{bulletArray}
223+
</ul>
224+
);
225+
}
226+
if (orderedArray.length > 0) {
227+
idArray.push(
228+
<ol className="notion-list notion-list-numbered " key={idArray.length}>
229+
{orderedArray}
230+
</ol>
231+
);
232+
}
233+
return <>{idArray}</>;
234+
};
235+
236+
interface NotionProps {
237+
blockMap: LoadPageChunkData["recordMap"]["block"];
238+
currentID: string;
239+
level: number;
240+
}
241+
242+
export const NotionRenderer: React.FC<NotionProps> = props => {
243+
const currentBlock = props.blockMap[props.currentID];
244+
245+
const renderChildren = !(
246+
currentBlock.value.type === "page" && props.level > 0
247+
);
248+
249+
return (
250+
<>
251+
<BlockRenderer level={props.level} block={currentBlock} />
252+
{currentBlock.value.content && renderChildren && (
253+
<ChildRenderer
254+
level={props.level}
255+
ids={currentBlock.value.content}
256+
blockMap={props.blockMap}
257+
/>
258+
)}
259+
</>
260+
);
261+
};

0 commit comments

Comments
 (0)