Skip to content

Commit

Permalink
Extract component for Notion page rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
SilentDepth committed Mar 20, 2023
1 parent e683ec0 commit b3187d1
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 150 deletions.
86 changes: 86 additions & 0 deletions components/NotionRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import dynamic from 'next/dynamic'
import { NotionRenderer as Renderer } from 'react-notion-x'

// Lazy-load some heavy components
const components = {
// Code block
Code: dynamic(() => {
return import('react-notion-x/build/third-party/code').then(async module => {
// Additional prismjs syntax
await Promise.all([
import('prismjs/components/prism-markup-templating'),
import('prismjs/components/prism-markup'),
import('prismjs/components/prism-bash'),
import('prismjs/components/prism-c'),
import('prismjs/components/prism-cpp'),
import('prismjs/components/prism-csharp'),
import('prismjs/components/prism-docker'),
import('prismjs/components/prism-java'),
import('prismjs/components/prism-js-templates'),
import('prismjs/components/prism-coffeescript'),
import('prismjs/components/prism-diff'),
import('prismjs/components/prism-git'),
import('prismjs/components/prism-go'),
import('prismjs/components/prism-graphql'),
import('prismjs/components/prism-handlebars'),
import('prismjs/components/prism-less'),
import('prismjs/components/prism-makefile'),
import('prismjs/components/prism-markdown'),
import('prismjs/components/prism-objectivec'),
import('prismjs/components/prism-ocaml'),
import('prismjs/components/prism-python'),
import('prismjs/components/prism-reason'),
import('prismjs/components/prism-rust'),
import('prismjs/components/prism-sass'),
import('prismjs/components/prism-scss'),
import('prismjs/components/prism-solidity'),
import('prismjs/components/prism-sql'),
import('prismjs/components/prism-stylus'),
import('prismjs/components/prism-swift'),
import('prismjs/components/prism-wasm'),
import('prismjs/components/prism-yaml')
])
return module.Code
})
}),
// Database block
Collection: dynamic(() => {
return import('react-notion-x/build/third-party/collection').then(module => module.Collection)
}),
// Equation block & inline variant
Equation: dynamic(() => {
return import('react-notion-x/build/third-party/equation').then(module => module.Equation)
}),
// PDF (Embed block)
Pdf: dynamic(() => {
return import('react-notion-x/build/third-party/pdf').then(module => module.Pdf)
}, { ssr: false }),
// Tweet block
Tweet: dynamic(() => {
return import('react-tweet-embed').then(module => {
const { TweetEmbed } = module
return function Tweet ({ id }) {
return <TweetEmbed tweetId={id} options={{ theme: 'dark' }} />
}
})
})
}

const mapPageUrl = id => `https://www.notion.so/${id.replace(/-/g, '')}`

/**
* Notion page renderer
*
* A wrapper of react-notion-x/NotionRenderer with predefined `components` and `mapPageUrl`
*
* @param props - Anything that react-notion-x/NotionRenderer supports
*/
export default function NotionRenderer (props) {
return (
<Renderer
components={components}
mapPageUrl={mapPageUrl}
{...props}
/>
)
}
79 changes: 79 additions & 0 deletions components/Post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import PropTypes from 'prop-types'
import Image from 'next/image'
import BLOG from '@/blog.config'
import useTheme from '@/lib/theme'
import { BlockMapProvider } from '@/lib/blockMap'
import formatDate from '@/lib/formatDate'
import TagItem from '@/components/TagItem'
import NotionRenderer from '@/components/NotionRenderer'
import TableOfContents from '@/components/TableOfContents'

/**
* A post renderer
*
* @param {PostProps} props
*
* @typedef {object} PostProps
* @prop {object} post - Post metadata
* @prop {object} blockMap - Post block data
* @prop {string} emailHash - Author email hash (for Gravatar)
*/
export default function Post (props) {
const { post, blockMap, emailHash } = props
const { dark } = useTheme()

return (
<BlockMapProvider blockMap={blockMap}>
<article>
<h1 className="font-bold text-3xl text-black dark:text-white">
{post.title}
</h1>
{post.type[0] !== 'Page' && (
<nav className="flex mt-7 items-start text-gray-500 dark:text-gray-400">
<div className="flex mb-4">
<a href={BLOG.socialLink || '#'} className="flex">
<Image
alt={BLOG.author}
width={24}
height={24}
src={`https://gravatar.com/avatar/${emailHash}`}
className="rounded-full"
/>
<p className="ml-2 md:block">{BLOG.author}</p>
</a>
<span className="block">&nbsp;/&nbsp;</span>
</div>
<div className="mr-2 mb-4 md:ml-0">
{formatDate(post.date?.start_date || post.createdTime, BLOG.lang)}
</div>
{post.tags && (
<div className="flex flex-nowrap max-w-full overflow-x-auto article-tags">
{post.tags.map(tag => (
<TagItem key={tag} tag={tag}/>
))}
</div>
)}
</nav>
)}
<div className="-mt-4 relative">
<NotionRenderer
recordMap={blockMap}
fullPage={false}
darkMode={dark}
/>
<div className="absolute left-full inset-y-0">
{/* `65px` is the height of expanded nav */}
{/* TODO: Remove the magic number */}
<TableOfContents className="sticky" style={{ top: '65px' }}/>
</div>
</div>
</article>
</BlockMapProvider>
)
}

Post.propTypes = {
post: PropTypes.object.isRequired,
blockMap: PropTypes.object.isRequired,
emailHash: PropTypes.string.isRequired
}
143 changes: 6 additions & 137 deletions layouts/layout.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,18 @@
import dynamic from 'next/dynamic'
import Image from 'next/image'
import Container from '@/components/Container'
import TagItem from '@/components/TagItem'
import { NotionRenderer } from 'react-notion-x'
import BLOG from '@/blog.config'
import formatDate from '@/lib/formatDate'
import { useLocale } from '@/lib/locale'
import useTheme from '@/lib/theme'
import { BlockMapProvider } from '@/lib/blockMap'
import { useRouter } from 'next/router'
import TableOfContents from '@/components/TableOfContents'
import Post from '@/components/Post'
import Comments from '@/components/Comments'

// -----------------------------------------------------------------------------
// dynamic imports for optional components
// -----------------------------------------------------------------------------

const Code = dynamic(() =>
import('react-notion-x/build/third-party/code').then(async (m) => {
// additional prism syntaxes
await Promise.all([
import('prismjs/components/prism-markup-templating.js'),
import('prismjs/components/prism-markup.js'),
import('prismjs/components/prism-bash.js'),
import('prismjs/components/prism-c.js'),
import('prismjs/components/prism-cpp.js'),
import('prismjs/components/prism-csharp.js'),
import('prismjs/components/prism-docker.js'),
import('prismjs/components/prism-java.js'),
import('prismjs/components/prism-js-templates.js'),
import('prismjs/components/prism-coffeescript.js'),
import('prismjs/components/prism-diff.js'),
import('prismjs/components/prism-git.js'),
import('prismjs/components/prism-go.js'),
import('prismjs/components/prism-graphql.js'),
import('prismjs/components/prism-handlebars.js'),
import('prismjs/components/prism-less.js'),
import('prismjs/components/prism-makefile.js'),
import('prismjs/components/prism-markdown.js'),
import('prismjs/components/prism-objectivec.js'),
import('prismjs/components/prism-ocaml.js'),
import('prismjs/components/prism-python.js'),
import('prismjs/components/prism-reason.js'),
import('prismjs/components/prism-rust.js'),
import('prismjs/components/prism-sass.js'),
import('prismjs/components/prism-scss.js'),
import('prismjs/components/prism-solidity.js'),
import('prismjs/components/prism-sql.js'),
import('prismjs/components/prism-stylus.js'),
import('prismjs/components/prism-swift.js'),
import('prismjs/components/prism-wasm.js'),
import('prismjs/components/prism-yaml.js')
])
return m.Code
})
)
const Collection = dynamic(() =>
import('react-notion-x/build/third-party/collection').then(
(m) => m.Collection
)
)
const Equation = dynamic(() =>
import('react-notion-x/build/third-party/equation').then((m) => m.Equation)
)
const Pdf = dynamic(
() => import('react-notion-x/build/third-party/pdf').then((m) => m.Pdf),
{
ssr: false
}
)
const Tweet = dynamic(() =>
import('react-tweet-embed').then(({ default: TweetEmbed }) => {
const Tweet = ({ id }) => <TweetEmbed tweetId={id} options={{ theme: 'dark' }} />
return Tweet
})
)

const mapPageUrl = id => {
return 'https://www.notion.so/' + id.replace(/-/g, '')
}

const Layout = ({
children,
blockMap,
frontMatter,
emailHash,
fullWidth = false
}) => {
const locale = useLocale()
const router = useRouter()
const { dark } = useTheme()

return (
<Container
Expand All @@ -101,65 +24,11 @@ const Layout = ({
type="article"
fullWidth={fullWidth}
>
<article>
<h1 className="font-bold text-3xl text-black dark:text-white">
{frontMatter.title}
</h1>
{frontMatter.type[0] !== 'Page' && (
<nav className="flex mt-7 items-start text-gray-500 dark:text-gray-400">
<div className="flex mb-4">
<a href={BLOG.socialLink || '#'} className="flex">
<Image
alt={BLOG.author}
width={24}
height={24}
src={`https://gravatar.com/avatar/${emailHash}`}
className="rounded-full"
/>
<p className="ml-2 md:block">{BLOG.author}</p>
</a>
<span className="block">&nbsp;/&nbsp;</span>
</div>
<div className="mr-2 mb-4 md:ml-0">
{formatDate(
frontMatter?.date?.start_date || frontMatter.createdTime,
BLOG.lang
)}
</div>
{frontMatter.tags && (
<div className="flex flex-nowrap max-w-full overflow-x-auto article-tags">
{frontMatter.tags.map(tag => (
<TagItem key={tag} tag={tag} />
))}
</div>
)}
</nav>
)}
{children}
{blockMap && (
<BlockMapProvider blockMap={blockMap}>
<div className="-mt-4 relative">
<NotionRenderer
recordMap={blockMap}
components={{
Code,
Collection,
Equation,
Pdf,
Tweet
}}
mapPageUrl={mapPageUrl}
darkMode={dark}
/>
<div className="absolute left-full inset-y-0">
{/* `65px` is the height of expanded nav */}
{/* TODO: Remove the magic number */}
<TableOfContents className="sticky" style={{ top: '65px' }} />
</div>
</div>
</BlockMapProvider>
)}
</article>
<Post
post={frontMatter}
blockMap={blockMap}
emailHash={emailHash}
/>
<div className="flex justify-between font-medium text-gray-500 dark:text-gray-400 my-5">
<a>
<button
Expand Down
17 changes: 4 additions & 13 deletions lib/rss.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@ import { Feed } from 'feed'
import BLOG from '@/blog.config'
import ReactDOMServer from 'react-dom/server'
import { getPostBlocks } from '@/lib/notion'
import { NotionRenderer, Equation, Code, Collection, CollectionRow } from 'react-notion-x'

const mapPageUrl = id => 'https://www.notion.so/' + id.replace(/-/g, '')
import NotionRenderer from '@/components/NotionRenderer'

const createFeedContent = async post => {
const content = ReactDOMServer.renderToString(<NotionRenderer
recordMap={await getPostBlocks(post.id)}
components={{
equation: Equation,
code: Code,
collection: Collection,
collectionRow: CollectionRow
}}
mapPageUrl={mapPageUrl}
/>)
const content = ReactDOMServer.renderToString(
<NotionRenderer recordMap={await getPostBlocks(post.id)} />
)
const regexExp = /<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
return content.replace(regexExp, '')
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"notion-client": "^6.16.0",
"notion-utils": "^6.16.0",
"preact": "^10.5.15",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-cusdis": "^2.1.3",
"react-dom": "^18.2.0",
Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b3187d1

Please sign in to comment.