Skip to content
Merged
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
7 changes: 2 additions & 5 deletions apps/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@types/prismjs": "^1.26.5",
"@types/styled-components": "^5.1.34",
"notion-to-jsx": "workspace:*",
"notion-to-utils": "workspace:*",
"prismjs": "^1.29.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"styled-components": "^6.1.14"
"react-dom": "^18.3.1"
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.4",
Expand Down
3 changes: 1 addition & 2 deletions apps/storybook/src/stories/Renderer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Renderer } from '../components/Renderer';
import { NotionBlock } from '../types';
import { Renderer, NotionBlock } from 'notion-to-jsx';

const meta = {
title: 'Components/Renderer',
Expand Down
1 change: 0 additions & 1 deletion apps/storybook/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
Expand Down
1 change: 0 additions & 1 deletion apps/storybook/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"scripts": {
"build": "turbo build --filter=!./apps/storybook",
"dev": "turbo dev --filter=!./apps/storybook",
"storybook": "turbo run storybook",
"build-storybook": "turbo run build-storybook",
"lint": "turbo lint --filter=!./apps/storybook",
"test": "turbo run test --filter=!./apps/storybook",
"test:watch": "turbo run test:watch --filter=!./apps/storybook",
Expand Down
35 changes: 31 additions & 4 deletions packages/notion-to-jsx/package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
{
"name": "notion-to-jsx",
"version": "0.0.0",
"description": "",
"description": "Notion blocks to JSX renderer",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/01-binary/notion-to-jsx",
"directory": "/packages/notion-to-jsx"
},
"scripts": {},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint .",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"files": [
"dist"
],
"sideEffects": false,
"keywords": [],
"keywords": [
"notion",
"jsx",
"react",
"renderer"
],
"author": "Jinsoo Lee",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"styled-components": "^6.0.0"
},
"dependencies": {
"prismjs": "^1.29.0",
"notion-to-utils": "workspace:*"
},
"devDependencies": {
"@types/prismjs": "^1.26.5",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/styled-components": "^5.1.34",
"typescript": "^5.6.3",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*"
"@repo/typescript-config": "workspace:*",
"tsup": "^8.0.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface OpenGraphData {
siteName: string;
}

interface BookmarkProps {
export interface BookmarkProps {
url: string;
caption?: RichTextItem[];
}
Expand Down Expand Up @@ -75,7 +75,7 @@ const fetchOpenGraphData = async (url: string): Promise<OpenGraphData> => {
title: new URL(url).hostname,
description: 'No description available',
image: '',
siteName: new URL(url).hostname.split('.')[1]
siteName: new URL(url).hostname.split('.')[1] as string,
};
};

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

return (
<a href={url} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }}>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: 'none' }}
>
<Card>
{ogData?.image && <PreviewImage src={ogData.image} alt={ogData.title} loading="lazy" />}
{ogData?.image && (
<PreviewImage src={ogData.image} alt={ogData.title} loading="lazy" />
)}
<Content>
<Title>{ogData?.title || url}</Title>
{ogData?.description && <Description>{ogData.description}</Description>}
{ogData?.description && (
<Description>{ogData.description}</Description>
)}
{ogData?.siteName && <SiteName>{ogData.siteName}</SiteName>}
{caption && caption.length > 0 && (
<Caption>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from 'styled-components';
import { RichTextItem } from '../types';
import { RichText } from './RichText';

interface ImageProps {
export interface ImageProps {
src: string;
alt: string;
caption?: RichTextItem[];
Expand Down Expand Up @@ -46,7 +46,12 @@ const Caption = styled.figcaption`
font-size: ${({ theme }) => theme.typography.fontSize.small};
`;

export const Image: React.FC<ImageProps> = ({ src, alt, caption, priority = false }) => {
export const Image: React.FC<ImageProps> = ({
src,
alt,
caption,
priority = false,
}) => {
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(false);

Expand All @@ -58,16 +63,8 @@ export const Image: React.FC<ImageProps> = ({ src, alt, caption, priority = fals
return (
<figure>
<ImageContainer>
{!isLoaded && !error && (
<Placeholder>
Loading...
</Placeholder>
)}
{error && (
<Placeholder>
Failed to load image
</Placeholder>
)}
{!isLoaded && !error && <Placeholder>Loading...</Placeholder>}
{error && <Placeholder>Failed to load image</Placeholder>}
<StyledImage
src={src}
alt={alt}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React from 'react';
import { RichText } from './RichText';
import { Image } from './Image';
import { Bookmark } from './Bookmark';
import { RichText, RichTextProps } from './RichText';
import { Image, ImageProps } from './Image';
import { Bookmark, BookmarkProps } from './Bookmark';
import { RichTextItem } from '../types';

export const MemoizedRichText = React.memo(RichText, (prev, next) => {
export const MemoizedRichText = React.memo<RichTextProps>(RichText, (prev, next) => {
return JSON.stringify(prev.richText) === JSON.stringify(next.richText);
});

export const MemoizedImage = React.memo(Image, (prev, next) => {
export const MemoizedImage = React.memo<ImageProps>(Image, (prev, next) => {
return (
prev.src === next.src &&
prev.alt === next.alt &&
JSON.stringify(prev.caption) === JSON.stringify(next.caption)
);
});

export const MemoizedBookmark = React.memo(Bookmark, (prev, next) => {
export const MemoizedBookmark = React.memo<BookmarkProps>(Bookmark, (prev, next) => {
return (
prev.url === next.url &&
JSON.stringify(prev.caption) === JSON.stringify(next.caption)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// import { Client } from 'notion-to-utils';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import Prism from 'prismjs';
import Prism, { Grammar } from 'prismjs';
import 'prismjs/themes/prism.css';
import 'prismjs/components/prism-typescript';
import 'prismjs/components/prism-javascript';
Expand Down Expand Up @@ -42,16 +42,16 @@ const notion = {
},
};

const GlobalStyle = createGlobalStyle<{ theme: Theme }>`
body {
background-color: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.text};
font-family: ${({ theme }) => theme.typography.fontFamily.base};
line-height: ${({ theme }) => theme.typography.lineHeight.base};
margin: 0;
padding: 0;
}
`;
// const GlobalStyle = createGlobalStyle<{ theme: Theme }>`
// body {
// background-color: ${({ theme }) => theme.colors.background};
// color: ${({ theme }) => theme.colors.text};
// font-family: ${({ theme }) => theme.typography.fontFamily.base};
// line-height: ${({ theme }) => theme.typography.lineHeight.base};
// margin: 0;
// padding: 0;
// }
// `;

const Container = styled.div`
max-width: 900px;
Expand Down Expand Up @@ -148,7 +148,7 @@ const CodeBlock: React.FC<{
const highlightedCode = useMemo(() => {
const prismLanguage =
Prism.languages[language] || Prism.languages.plaintext;
return Prism.highlight(code, prismLanguage, language);
return Prism.highlight(code, prismLanguage as Grammar, language);
}, [code, language]);

return (
Expand All @@ -173,7 +173,9 @@ const ListBlocksRenderer: React.FC<{
}> = ({ blocks, startIndex, type }) => {
let consecutiveItems = 0;
for (let i = startIndex; i < blocks.length; i++) {
if (blocks[i].type === `${type}_list_item`) {
const block = blocks[i];
if (!block) break;
if (block.type === `${type}_list_item`) {
consecutiveItems++;
} else {
break;
Expand All @@ -182,9 +184,15 @@ const ListBlocksRenderer: React.FC<{

return (
<List as={type === 'numbered' ? 'ol' : 'ul'} type={type}>
{blocks.slice(startIndex, startIndex + consecutiveItems).map((block) => (
<BlockRenderer key={block.id} block={block} />
))}
{blocks
.slice(startIndex, startIndex + consecutiveItems)
.map((block, index) => (
<BlockRenderer
key={block.id}
block={block}
index={startIndex + index}
/>
))}
</List>
);
};
Expand Down Expand Up @@ -237,15 +245,15 @@ const BlockRenderer: React.FC<{
);
case 'bulleted_list_item':
return (
<ListItem ref={ref} tabIndex={0} role="listitem">
<ListItem ref={ref as any} tabIndex={0} role="listitem">
{block.bulleted_list_item?.rich_text && (
<MemoizedRichText richText={block.bulleted_list_item.rich_text} />
)}
</ListItem>
);
case 'numbered_list_item':
return (
<ListItem ref={ref} tabIndex={0} role="listitem">
<ListItem ref={ref as any} tabIndex={0} role="listitem">
{block.numbered_list_item?.rich_text && (
<MemoizedRichText richText={block.numbered_list_item.rich_text} />
)}
Expand Down Expand Up @@ -347,10 +355,11 @@ export const Renderer: React.FC<{

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

if (
block.type === 'bulleted_list_item' &&
(i === 0 || blocks[i - 1].type !== 'bulleted_list_item')
(i === 0 || blocks[i - 1]?.type !== 'bulleted_list_item')
) {
result.push(
<List
Expand All @@ -369,18 +378,19 @@ export const Renderer: React.FC<{
);
while (
i + 1 < blocks.length &&
blocks[i + 1].type === 'bulleted_list_item'
blocks[i + 1] &&
blocks[i + 1]?.type === 'bulleted_list_item'
) {
i++;
}
} else if (
block.type === 'numbered_list_item' &&
(i === 0 || blocks[i - 1].type !== 'numbered_list_item')
(i === 0 || blocks[i - 1]?.type !== 'numbered_list_item')
) {
result.push(
<List
as="ol"
type="numbered"
type="1"
role="list"
aria-label="Numbered list"
key={block.id}
Expand All @@ -394,7 +404,8 @@ export const Renderer: React.FC<{
);
while (
i + 1 < blocks.length &&
blocks[i + 1].type === 'numbered_list_item'
blocks[i + 1] &&
blocks[i + 1]?.type === 'numbered_list_item'
) {
i++;
}
Expand All @@ -418,7 +429,7 @@ export const Renderer: React.FC<{

return (
<ThemeProvider theme={theme}>
<GlobalStyle />
{/* <GlobalStyle /> */}
<Container role="main" aria-label="Notion page content">
{renderedBlocks}
</Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ const StyledText = styled.span<StyledTextProps>`
}};
`;

interface RichTextProps {
export interface RichTextProps {
richText: RichTextItem[];
}

export const RichText: React.FC<RichTextProps> = ({ richText }) => {
return (
<>
{richText.map((text, index) => {
const { bold, italic, strikethrough, underline, code, color } = text.annotations;
const { bold, italic, strikethrough, underline, code, color } =
text.annotations;

const content = text.text.link ? (
<a
Expand Down
4 changes: 4 additions & 0 deletions packages/notion-to-jsx/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './components/Renderer';
export * from './components/MemoizedComponents';
export * from './types';
export * from './styles/theme';
Loading