Skip to content

Commit 67dd38e

Browse files
authored
Notion to jsx init (#17)
* chore: 스토리북 컴포넌트 관련 파일 삭제 * chore: notion-to-jsx tsconfig, tsup 관련 설정 * feat(notion-to-jsx): 초기 렌더 컴포넌트 추가 * build: 스토리북 관련 config 설정 수정
1 parent d731674 commit 67dd38e

21 files changed

+207
-88
lines changed

apps/storybook/package.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@
1010
"build-storybook": "storybook build"
1111
},
1212
"dependencies": {
13-
"@types/prismjs": "^1.26.5",
14-
"@types/styled-components": "^5.1.34",
13+
"notion-to-jsx": "workspace:*",
1514
"notion-to-utils": "workspace:*",
16-
"prismjs": "^1.29.0",
1715
"react": "^18.3.1",
18-
"react-dom": "^18.3.1",
19-
"styled-components": "^6.1.14"
16+
"react-dom": "^18.3.1"
2017
},
2118
"devDependencies": {
2219
"@chromatic-com/storybook": "^3.2.4",

apps/storybook/src/stories/Renderer.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Meta, StoryObj } from '@storybook/react';
2-
import { Renderer } from '../components/Renderer';
3-
import { NotionBlock } from '../types';
2+
import { Renderer, NotionBlock } from 'notion-to-jsx';
43

54
const meta = {
65
title: 'Components/Renderer',

apps/storybook/tsconfig.app.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"compilerOptions": {
3-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
43
"target": "ES2020",
54
"useDefineForClassFields": true,
65
"lib": ["ES2020", "DOM", "DOM.Iterable"],

apps/storybook/tsconfig.node.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"compilerOptions": {
3-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
43
"target": "ES2022",
54
"lib": ["ES2023"],
65
"module": "ESNext",

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"scripts": {
66
"build": "turbo build --filter=!./apps/storybook",
77
"dev": "turbo dev --filter=!./apps/storybook",
8+
"storybook": "turbo run storybook",
9+
"build-storybook": "turbo run build-storybook",
810
"lint": "turbo lint --filter=!./apps/storybook",
911
"test": "turbo run test --filter=!./apps/storybook",
1012
"test:watch": "turbo run test:watch --filter=!./apps/storybook",

packages/notion-to-jsx/package.json

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,51 @@
11
{
22
"name": "notion-to-jsx",
33
"version": "0.0.0",
4-
"description": "",
4+
"description": "Notion blocks to JSX renderer",
55
"private": true,
66
"repository": {
77
"type": "git",
88
"url": "https://github.com/01-binary/notion-to-jsx",
99
"directory": "/packages/notion-to-jsx"
1010
},
11-
"scripts": {},
11+
"main": "./dist/index.js",
12+
"module": "./dist/index.mjs",
13+
"types": "./dist/index.d.ts",
14+
"scripts": {
15+
"build": "tsup",
16+
"dev": "tsup --watch",
17+
"lint": "eslint .",
18+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
19+
},
1220
"files": [
1321
"dist"
1422
],
1523
"sideEffects": false,
16-
"keywords": [],
24+
"keywords": [
25+
"notion",
26+
"jsx",
27+
"react",
28+
"renderer"
29+
],
1730
"author": "Jinsoo Lee",
1831
"license": "MIT",
32+
"peerDependencies": {
33+
"react": "^18.0.0",
34+
"react-dom": "^18.0.0",
35+
"styled-components": "^6.0.0"
36+
},
37+
"dependencies": {
38+
"prismjs": "^1.29.0",
39+
"notion-to-utils": "workspace:*"
40+
},
1941
"devDependencies": {
42+
"@types/prismjs": "^1.26.5",
43+
"@types/react": "^18.3.18",
44+
"@types/react-dom": "^18.3.5",
45+
"@types/styled-components": "^5.1.34",
2046
"typescript": "^5.6.3",
2147
"@repo/eslint-config": "workspace:*",
22-
"@repo/typescript-config": "workspace:*"
48+
"@repo/typescript-config": "workspace:*",
49+
"tsup": "^8.0.0"
2350
}
2451
}

apps/storybook/src/components/Bookmark.tsx renamed to packages/notion-to-jsx/src/components/Bookmark.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface OpenGraphData {
1010
siteName: string;
1111
}
1212

13-
interface BookmarkProps {
13+
export interface BookmarkProps {
1414
url: string;
1515
caption?: RichTextItem[];
1616
}
@@ -75,7 +75,7 @@ const fetchOpenGraphData = async (url: string): Promise<OpenGraphData> => {
7575
title: new URL(url).hostname,
7676
description: 'No description available',
7777
image: '',
78-
siteName: new URL(url).hostname.split('.')[1]
78+
siteName: new URL(url).hostname.split('.')[1] as string,
7979
};
8080
};
8181

@@ -97,12 +97,21 @@ export const Bookmark: React.FC<BookmarkProps> = ({ url, caption }) => {
9797
}, [url]);
9898

9999
return (
100-
<a href={url} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }}>
100+
<a
101+
href={url}
102+
target="_blank"
103+
rel="noopener noreferrer"
104+
style={{ textDecoration: 'none' }}
105+
>
101106
<Card>
102-
{ogData?.image && <PreviewImage src={ogData.image} alt={ogData.title} loading="lazy" />}
107+
{ogData?.image && (
108+
<PreviewImage src={ogData.image} alt={ogData.title} loading="lazy" />
109+
)}
103110
<Content>
104111
<Title>{ogData?.title || url}</Title>
105-
{ogData?.description && <Description>{ogData.description}</Description>}
112+
{ogData?.description && (
113+
<Description>{ogData.description}</Description>
114+
)}
106115
{ogData?.siteName && <SiteName>{ogData.siteName}</SiteName>}
107116
{caption && caption.length > 0 && (
108117
<Caption>

apps/storybook/src/components/Image.tsx renamed to packages/notion-to-jsx/src/components/Image.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled from 'styled-components';
33
import { RichTextItem } from '../types';
44
import { RichText } from './RichText';
55

6-
interface ImageProps {
6+
export interface ImageProps {
77
src: string;
88
alt: string;
99
caption?: RichTextItem[];
@@ -46,7 +46,12 @@ const Caption = styled.figcaption`
4646
font-size: ${({ theme }) => theme.typography.fontSize.small};
4747
`;
4848

49-
export const Image: React.FC<ImageProps> = ({ src, alt, caption, priority = false }) => {
49+
export const Image: React.FC<ImageProps> = ({
50+
src,
51+
alt,
52+
caption,
53+
priority = false,
54+
}) => {
5055
const [isLoaded, setIsLoaded] = useState(false);
5156
const [error, setError] = useState(false);
5257

@@ -58,16 +63,8 @@ export const Image: React.FC<ImageProps> = ({ src, alt, caption, priority = fals
5863
return (
5964
<figure>
6065
<ImageContainer>
61-
{!isLoaded && !error && (
62-
<Placeholder>
63-
Loading...
64-
</Placeholder>
65-
)}
66-
{error && (
67-
<Placeholder>
68-
Failed to load image
69-
</Placeholder>
70-
)}
66+
{!isLoaded && !error && <Placeholder>Loading...</Placeholder>}
67+
{error && <Placeholder>Failed to load image</Placeholder>}
7168
<StyledImage
7269
src={src}
7370
alt={alt}

apps/storybook/src/components/MemoizedComponents.tsx renamed to packages/notion-to-jsx/src/components/MemoizedComponents.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import React from 'react';
2-
import { RichText } from './RichText';
3-
import { Image } from './Image';
4-
import { Bookmark } from './Bookmark';
2+
import { RichText, RichTextProps } from './RichText';
3+
import { Image, ImageProps } from './Image';
4+
import { Bookmark, BookmarkProps } from './Bookmark';
55
import { RichTextItem } from '../types';
66

7-
export const MemoizedRichText = React.memo(RichText, (prev, next) => {
7+
export const MemoizedRichText = React.memo<RichTextProps>(RichText, (prev, next) => {
88
return JSON.stringify(prev.richText) === JSON.stringify(next.richText);
99
});
1010

11-
export const MemoizedImage = React.memo(Image, (prev, next) => {
11+
export const MemoizedImage = React.memo<ImageProps>(Image, (prev, next) => {
1212
return (
1313
prev.src === next.src &&
1414
prev.alt === next.alt &&
1515
JSON.stringify(prev.caption) === JSON.stringify(next.caption)
1616
);
1717
});
1818

19-
export const MemoizedBookmark = React.memo(Bookmark, (prev, next) => {
19+
export const MemoizedBookmark = React.memo<BookmarkProps>(Bookmark, (prev, next) => {
2020
return (
2121
prev.url === next.url &&
2222
JSON.stringify(prev.caption) === JSON.stringify(next.caption)

apps/storybook/src/components/Renderer.tsx renamed to packages/notion-to-jsx/src/components/Renderer.tsx

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// import { Client } from 'notion-to-utils';
22
import React, { useEffect, useState, useMemo, useCallback } from 'react';
33
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
4-
import Prism from 'prismjs';
4+
import Prism, { Grammar } from 'prismjs';
55
import 'prismjs/themes/prism.css';
66
import 'prismjs/components/prism-typescript';
77
import 'prismjs/components/prism-javascript';
@@ -42,16 +42,16 @@ const notion = {
4242
},
4343
};
4444

45-
const GlobalStyle = createGlobalStyle<{ theme: Theme }>`
46-
body {
47-
background-color: ${({ theme }) => theme.colors.background};
48-
color: ${({ theme }) => theme.colors.text};
49-
font-family: ${({ theme }) => theme.typography.fontFamily.base};
50-
line-height: ${({ theme }) => theme.typography.lineHeight.base};
51-
margin: 0;
52-
padding: 0;
53-
}
54-
`;
45+
// const GlobalStyle = createGlobalStyle<{ theme: Theme }>`
46+
// body {
47+
// background-color: ${({ theme }) => theme.colors.background};
48+
// color: ${({ theme }) => theme.colors.text};
49+
// font-family: ${({ theme }) => theme.typography.fontFamily.base};
50+
// line-height: ${({ theme }) => theme.typography.lineHeight.base};
51+
// margin: 0;
52+
// padding: 0;
53+
// }
54+
// `;
5555

5656
const Container = styled.div`
5757
max-width: 900px;
@@ -148,7 +148,7 @@ const CodeBlock: React.FC<{
148148
const highlightedCode = useMemo(() => {
149149
const prismLanguage =
150150
Prism.languages[language] || Prism.languages.plaintext;
151-
return Prism.highlight(code, prismLanguage, language);
151+
return Prism.highlight(code, prismLanguage as Grammar, language);
152152
}, [code, language]);
153153

154154
return (
@@ -173,7 +173,9 @@ const ListBlocksRenderer: React.FC<{
173173
}> = ({ blocks, startIndex, type }) => {
174174
let consecutiveItems = 0;
175175
for (let i = startIndex; i < blocks.length; i++) {
176-
if (blocks[i].type === `${type}_list_item`) {
176+
const block = blocks[i];
177+
if (!block) break;
178+
if (block.type === `${type}_list_item`) {
177179
consecutiveItems++;
178180
} else {
179181
break;
@@ -182,9 +184,15 @@ const ListBlocksRenderer: React.FC<{
182184

183185
return (
184186
<List as={type === 'numbered' ? 'ol' : 'ul'} type={type}>
185-
{blocks.slice(startIndex, startIndex + consecutiveItems).map((block) => (
186-
<BlockRenderer key={block.id} block={block} />
187-
))}
187+
{blocks
188+
.slice(startIndex, startIndex + consecutiveItems)
189+
.map((block, index) => (
190+
<BlockRenderer
191+
key={block.id}
192+
block={block}
193+
index={startIndex + index}
194+
/>
195+
))}
188196
</List>
189197
);
190198
};
@@ -237,15 +245,15 @@ const BlockRenderer: React.FC<{
237245
);
238246
case 'bulleted_list_item':
239247
return (
240-
<ListItem ref={ref} tabIndex={0} role="listitem">
248+
<ListItem ref={ref as any} tabIndex={0} role="listitem">
241249
{block.bulleted_list_item?.rich_text && (
242250
<MemoizedRichText richText={block.bulleted_list_item.rich_text} />
243251
)}
244252
</ListItem>
245253
);
246254
case 'numbered_list_item':
247255
return (
248-
<ListItem ref={ref} tabIndex={0} role="listitem">
256+
<ListItem ref={ref as any} tabIndex={0} role="listitem">
249257
{block.numbered_list_item?.rich_text && (
250258
<MemoizedRichText richText={block.numbered_list_item.rich_text} />
251259
)}
@@ -347,10 +355,11 @@ export const Renderer: React.FC<{
347355

348356
for (let i = 0; i < blocks.length; i++) {
349357
const block = blocks[i];
358+
if (!block) break;
350359

351360
if (
352361
block.type === 'bulleted_list_item' &&
353-
(i === 0 || blocks[i - 1].type !== 'bulleted_list_item')
362+
(i === 0 || blocks[i - 1]?.type !== 'bulleted_list_item')
354363
) {
355364
result.push(
356365
<List
@@ -369,18 +378,19 @@ export const Renderer: React.FC<{
369378
);
370379
while (
371380
i + 1 < blocks.length &&
372-
blocks[i + 1].type === 'bulleted_list_item'
381+
blocks[i + 1] &&
382+
blocks[i + 1]?.type === 'bulleted_list_item'
373383
) {
374384
i++;
375385
}
376386
} else if (
377387
block.type === 'numbered_list_item' &&
378-
(i === 0 || blocks[i - 1].type !== 'numbered_list_item')
388+
(i === 0 || blocks[i - 1]?.type !== 'numbered_list_item')
379389
) {
380390
result.push(
381391
<List
382392
as="ol"
383-
type="numbered"
393+
type="1"
384394
role="list"
385395
aria-label="Numbered list"
386396
key={block.id}
@@ -394,7 +404,8 @@ export const Renderer: React.FC<{
394404
);
395405
while (
396406
i + 1 < blocks.length &&
397-
blocks[i + 1].type === 'numbered_list_item'
407+
blocks[i + 1] &&
408+
blocks[i + 1]?.type === 'numbered_list_item'
398409
) {
399410
i++;
400411
}
@@ -418,7 +429,7 @@ export const Renderer: React.FC<{
418429

419430
return (
420431
<ThemeProvider theme={theme}>
421-
<GlobalStyle />
432+
{/* <GlobalStyle /> */}
422433
<Container role="main" aria-label="Notion page content">
423434
{renderedBlocks}
424435
</Container>

0 commit comments

Comments
 (0)