├── .github
├── funding.yml
├── issue_template.md
├── pull_request_template.md
└── workflows
│ ├── release.yml
│ └── test.yml
├── examples
├── minimal
│ ├── public
│ │ ├── robots.txt
│ │ └── favicon.ico
│ ├── styles
│ │ └── globals.css
│ ├── lib
│ │ ├── notion.ts
│ │ └── config.ts
│ ├── turbo.json
│ ├── tsconfig.json
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── pages
│ │ ├── _document.tsx
│ │ ├── _app.tsx
│ │ ├── index.tsx
│ │ └── [pageId].tsx
│ ├── package.json
│ ├── components
│ │ └── NotionPage.tsx
│ └── readme.md
├── full
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ └── social.jpg
│ ├── styles
│ │ └── globals.css
│ ├── turbo.json
│ ├── tsconfig.json
│ ├── components
│ │ ├── Loading.tsx
│ │ ├── styles.module.css
│ │ └── LoadingIcon.tsx
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── pages
│ │ ├── _document.tsx
│ │ ├── _app.tsx
│ │ └── index.tsx
│ ├── package.json
│ └── lib
│ │ ├── config.ts
│ │ ├── notion.ts
│ │ └── preview-images.ts
└── cra
│ ├── src
│ ├── react-app-env.d.ts
│ ├── config.ts
│ ├── index.css
│ ├── index.tsx
│ └── App.tsx
│ ├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
│ ├── tsconfig.json
│ ├── .gitignore
│ └── package.json
├── .prettierignore
├── packages
├── notion-utils
│ ├── src
│ │ ├── reset.d.ts
│ │ ├── is-url.ts
│ │ ├── uuid-to-id.ts
│ │ ├── id-to-uuid.ts
│ │ ├── map-page-url.ts
│ │ ├── format-date.ts
│ │ ├── normalize-title.ts
│ │ ├── get-page-title.ts
│ │ ├── format-notion-date-time.ts
│ │ ├── get-date-value.ts
│ │ ├── get-page-tweet-ids.ts
│ │ ├── get-text-content.ts
│ │ ├── get-page-tweet-urls.ts
│ │ ├── __snapshots__
│ │ │ └── normalize-url.test.ts.snap
│ │ ├── get-block-collection-id.ts
│ │ ├── get-block-icon.ts
│ │ ├── normalize-url.ts
│ │ ├── get-block-title.ts
│ │ ├── parse-page-id.ts
│ │ ├── merge-record-maps.ts
│ │ ├── index.ts
│ │ ├── get-canonical-page-id.ts
│ │ ├── get-page-breadcrumbs.ts
│ │ ├── get-block-parent-page.ts
│ │ ├── normalize-url.test.ts
│ │ └── map-image-url.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── fixtures
│ │ └── nba
│ │ │ ├── data
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-d4102177-3cb3-4d36-8f39-92d84147f825.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-1a1863bd-f819-41dc-a228-831ca7a75d2a.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-9a43758f-21c5-4fc2-a4b5-0fc82971b79f.json
│ │ │ ├── e777a528-9404-4e96-9f26-0014be705592-c01c2f48-5442-47d8-adb3-01c4f8bb8e58.json
│ │ │ ├── e777a528-9404-4e96-9f26-0014be705592-c0d2d91c-e2a5-4ea5-a9b7-2b2d8ae2d591.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-048d8535-b957-488a-89f0-ade8d3c4c026.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-59a7246f-e923-4faf-9246-75851294365b.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-5ae81e49-33ad-47bf-9c14-14adb9145b4d.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-693b4304-0bbd-4f9a-85f1-7c9cb1768110.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-c69e1799-88cb-4135-a10d-1ec4617f0aa6.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-cfe27a86-00d0-4322-b2db-2bc3e69269e2.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-d8f02741-e03a-4e40-91d7-0b821ee4014d.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-e3d28d4f-e72e-430a-92f8-263cd01ea452.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-e8feed24-4bf6-41b5-84a7-6134f9160cc1.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-367ecf41-f124-4652-bb7d-464b3789aefd.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-3815e8d7-e5f2-4399-a761-9ceaed565257.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-385ed123-6a39-46e5-984c-9704bcfd6494.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-448a76b8-847d-4a40-a494-9a6da9c41a58.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-52718a79-d339-4d26-873f-734004af8823.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-5662c1f4-a011-4629-9616-3a2d53f6c9af.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-5742eaed-3c36-495a-bde5-7d33cba0ddb5.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-5c20b593-23d4-4884-9102-3ef3b1049273.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-63717bf3-3af6-4112-989d-acbe665ccf91.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-7687ad1f-ccbe-4abb-8e95-a3685543aefe.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-85e4d626-09cc-45f6-9a28-07d5d490ca1d.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-93fe8a38-e32b-4c6a-87f2-03f1a438de75.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-99cd3dfb-97fb-493d-bfaa-518bd872dda4.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-9fdf39a1-64c3-464c-a4c9-f3e4d9a9aa2c.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-ab6c9a56-5322-4bd1-a384-7797a81478c5.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-b6c6b669-659e-4487-8d48-8a538b8a0d82.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-bdc489ac-13dd-46bd-97f9-86866d76bc43.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-c14e33fe-19d2-4266-a935-0ef6528a77ff.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-c34ab79e-df2d-4a6d-af71-8aef61a65aa7.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-dac0d378-1774-47fc-a3a3-103ae120a3c1.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-bd226193-3647-4ed1-bbe3-a5baa69954c7.json
│ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-1d79cc15-d040-43eb-9d65-7e35bafbcf74.json
│ │ │ └── 1a425309-21c0-4ca1-8548-c5915137b566-43d091fb-a648-40cc-ae10-9d9784c34524.json
│ │ │ ├── e777a528-9404-4e96-9f26-0014be705592.json
│ │ │ └── List.json
│ ├── package.json
│ └── readme.md
├── notion-client
│ ├── src
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── notion-api.test.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── fixtures
│ │ ├── signed-urls.json
│ │ ├── board-results.json
│ │ ├── blocks
│ │ │ ├── audio.json
│ │ │ ├── equation.json
│ │ │ ├── file.json
│ │ │ └── drive.json
│ │ └── search.json
│ └── package.json
├── notion-compat
│ ├── tsconfig.json
│ ├── src
│ │ ├── index.ts
│ │ ├── convert-time.ts
│ │ ├── convert-color.ts
│ │ ├── types.ts
│ │ └── notion-compat-api.test.ts
│ ├── tsup.config.ts
│ └── package.json
├── notion-types
│ ├── tsconfig.json
│ ├── src
│ │ ├── index.ts
│ │ ├── user.ts
│ │ ├── api.ts
│ │ └── collection.ts
│ ├── tsup.config.ts
│ ├── package.json
│ └── readme.md
└── react-notion-x
│ ├── src
│ ├── third-party
│ │ ├── modal.tsx
│ │ ├── readme.md
│ │ ├── collection-column-title.tsx
│ │ ├── pdf.tsx
│ │ ├── collections.md
│ │ ├── collection-view.tsx
│ │ ├── collection-group.tsx
│ │ ├── equation.tsx
│ │ └── collection-utils.ts
│ ├── index.ts
│ ├── icons
│ │ ├── check.tsx
│ │ ├── type-relation.svg
│ │ ├── type-relation.tsx
│ │ ├── check.svg
│ │ ├── chevron-down-icon.tsx
│ │ ├── type-select.tsx
│ │ ├── default-page-icon.tsx
│ │ ├── collection-view-board.tsx
│ │ ├── collection-view-list.tsx
│ │ ├── file-icon.tsx
│ │ ├── clear-icon.tsx
│ │ ├── type-multi-select.tsx
│ │ ├── collection-view-gallery.tsx
│ │ ├── type-date.tsx
│ │ ├── type-select.svg
│ │ ├── collection-view-table.tsx
│ │ ├── type-text.tsx
│ │ ├── type-person-2.tsx
│ │ ├── collection-view-board.svg
│ │ ├── collection-view-list.svg
│ │ ├── type-number.tsx
│ │ ├── copy.svg
│ │ ├── type-checkbox.tsx
│ │ ├── type-timestamp.tsx
│ │ ├── collection-view-gallery.svg
│ │ ├── type-formula.tsx
│ │ ├── type-url.tsx
│ │ ├── link-icon.tsx
│ │ ├── type-person-2.svg
│ │ ├── type-file.tsx
│ │ ├── type-phone-number.tsx
│ │ ├── type-date.svg
│ │ ├── copy.tsx
│ │ ├── empty-icon.tsx
│ │ ├── search-icon.tsx
│ │ ├── collection-view-icon.tsx
│ │ ├── type-checkbox.svg
│ │ ├── type-multi-select.svg
│ │ ├── type-text.svg
│ │ ├── type-title.tsx
│ │ ├── collection-view-table.svg
│ │ ├── type-person.tsx
│ │ ├── type-phone-number.svg
│ │ ├── type-formula.svg
│ │ ├── type-file.svg
│ │ ├── type-auto-increment-id.svg
│ │ ├── type-number.svg
│ │ ├── type-auto-increment-id.tsx
│ │ ├── type-url.svg
│ │ ├── type-email.tsx
│ │ ├── type-timestamp.svg
│ │ ├── type-person.svg
│ │ ├── loading-icon.tsx
│ │ ├── property-icon.tsx
│ │ ├── collection-view-calendar.tsx
│ │ ├── type-status.svg
│ │ └── type-status.tsx
│ └── components
│ │ ├── graceful-image.tsx
│ │ ├── checkbox.tsx
│ │ ├── audio.tsx
│ │ ├── sync-pointer-block.tsx
│ │ ├── file.tsx
│ │ ├── page-title.tsx
│ │ └── mention-preview-card.tsx
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── readme.md
├── media
├── notion-ts.png
├── super-so-banner.png
└── react-notion-x-perf.png
├── .editorconfig
├── tsconfig.json
├── .gitignore
├── eslint.config.js
├── turbo.json
├── .vscode
└── launch.json
├── license
├── pnpm-workspace.yaml
├── package.json
└── contributing.md
/.github/funding.yml:
--------------------------------------------------------------------------------
1 | github: [transitive-bullshit]
2 |
--------------------------------------------------------------------------------
/examples/minimal/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: *
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .snapshots/
2 | .next/
3 | .vercel/
4 | build/
5 | docs/
6 |
--------------------------------------------------------------------------------
/examples/full/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Disallow: /api/*
--------------------------------------------------------------------------------
/packages/notion-utils/src/reset.d.ts:
--------------------------------------------------------------------------------
1 | import '@total-typescript/ts-reset'
2 |
--------------------------------------------------------------------------------
/examples/cra/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/is-url.ts:
--------------------------------------------------------------------------------
1 | export { default as isUrl } from 'is-url-superb'
2 |
--------------------------------------------------------------------------------
/media/notion-ts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/media/notion-ts.png
--------------------------------------------------------------------------------
/packages/notion-client/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './notion-api'
2 | export * from './types'
3 |
--------------------------------------------------------------------------------
/media/super-so-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/media/super-so-banner.png
--------------------------------------------------------------------------------
/examples/cra/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/uuid-to-id.ts:
--------------------------------------------------------------------------------
1 | export const uuidToId = (uuid: string) => uuid.replaceAll('-', '')
2 |
--------------------------------------------------------------------------------
/media/react-notion-x-perf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/media/react-notion-x-perf.png
--------------------------------------------------------------------------------
/examples/cra/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/examples/cra/public/favicon.ico
--------------------------------------------------------------------------------
/examples/cra/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/examples/cra/public/logo192.png
--------------------------------------------------------------------------------
/examples/cra/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/examples/cra/public/logo512.png
--------------------------------------------------------------------------------
/examples/full/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/examples/full/public/favicon.ico
--------------------------------------------------------------------------------
/examples/full/public/social.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/examples/full/public/social.jpg
--------------------------------------------------------------------------------
/examples/minimal/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotionX/react-notion-x/HEAD/examples/minimal/public/favicon.ico
--------------------------------------------------------------------------------
/examples/full/styles/globals.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | padding: 0;
7 | margin: 0;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/minimal/styles/globals.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | padding: 0;
7 | margin: 0;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/minimal/lib/notion.ts:
--------------------------------------------------------------------------------
1 | import { NotionAPI } from 'notion-client'
2 |
3 | const notion = new NotionAPI()
4 | export default notion
5 |
--------------------------------------------------------------------------------
/examples/full/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": [".next/**", "!.next/cache/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/minimal/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["//"],
3 | "tasks": {
4 | "build": {
5 | "outputs": [".next/**", "!.next/cache/**"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/notion-client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "build"
5 | },
6 | "include": ["src", "*.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/notion-compat/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "build"
5 | },
6 | "include": ["src", "*.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/notion-types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "build"
5 | },
6 | "include": ["src", "*.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/notion-utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "build"
5 | },
6 | "include": ["src", "*.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/modal.tsx:
--------------------------------------------------------------------------------
1 | import Modal from 'react-modal'
2 |
3 | Modal.setAppElement('.notion-frame')
4 |
5 | export { default as Modal } from 'react-modal'
6 |
--------------------------------------------------------------------------------
/packages/react-notion-x/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "build"
5 | },
6 | "include": ["src", "*.config.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/id-to-uuid.ts:
--------------------------------------------------------------------------------
1 | export const idToUuid = (id = '') =>
2 | `${id.slice(0, 8)}-${id.slice(8, 12)}-${id.slice(12, 16)}-${id.slice(
3 | 16,
4 | 20
5 | )}-${id.slice(20)}`
6 |
--------------------------------------------------------------------------------
/examples/full/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@fisch0920/config/tsconfig-react",
3 | "compilerOptions": {
4 | "target": "ES2017"
5 | },
6 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
7 | "exclude": ["node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/examples/minimal/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@fisch0920/config/tsconfig-react",
3 | "compilerOptions": {
4 | "target": "ES2017"
5 | },
6 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
7 | "exclude": ["node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/notion-compat/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './convert-block'
2 | export * from './convert-color'
3 | export * from './convert-page'
4 | export * from './convert-rich-text'
5 | export * from './convert-time'
6 | export * from './notion-compat-api'
7 |
--------------------------------------------------------------------------------
/examples/minimal/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/packages/notion-types/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api'
2 | export * from './block'
3 | export * from './collection'
4 | export * from './collection-view'
5 | export * from './core'
6 | export * from './formula'
7 | export * from './maps'
8 | export * from './user'
9 |
--------------------------------------------------------------------------------
/examples/cra/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@fisch0920/config/tsconfig-react",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "jsx": "react-jsx",
6 | "moduleResolution": "node"
7 | },
8 | "include": ["src"],
9 | "exclude": ["node_modules"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/full/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { LoadingIcon } from './LoadingIcon'
2 | import styles from './styles.module.css'
3 |
4 | export function Loading() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components/header'
2 | export * from './components/page-icon'
3 | export * from './components/text'
4 | export * from './context'
5 | export { NotionRenderer } from './renderer'
6 | export * from './types'
7 | export * from './utils'
8 |
--------------------------------------------------------------------------------
/examples/full/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | import './.next/types/routes.d.ts'
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
7 |
--------------------------------------------------------------------------------
/packages/notion-compat/src/convert-time.ts:
--------------------------------------------------------------------------------
1 | export function convertTime(time?: string): number | undefined {
2 | if (time) {
3 | try {
4 | return new Date(time).getTime()
5 | } catch {
6 | // ignore invalid time strings
7 | }
8 | }
9 |
10 | return undefined
11 | }
12 |
--------------------------------------------------------------------------------
/examples/cra/src/config.ts:
--------------------------------------------------------------------------------
1 | // TODO: change these to your own values
2 | // NOTE: rootNotionSpaceId is optional; set it to undefined if you don't want to
3 | // use it.
4 | export const rootNotionPageId = '067dd719a912471ea9a3ac10710e7fdf'
5 | export const rootNotionSpaceId = 'fde5ac74-eea3-4527-8f00-4482710e1af3'
6 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/map-page-url.ts:
--------------------------------------------------------------------------------
1 | export const defaultMapPageUrl = (rootPageId?: string) => (pageId: string) => {
2 | pageId = (pageId || '').replaceAll('-', '')
3 |
4 | if (rootPageId && pageId === rootPageId) {
5 | return '/'
6 | } else {
7 | return `/${pageId}`
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/minimal/next.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | staticPageGenerationTimeout: 300,
3 | images: {
4 | domains: [
5 | 'www.notion.so',
6 | 'notion.so',
7 | 'images.unsplash.com',
8 | 'pbs.twimg.com'
9 | ],
10 | formats: ['image/avif', 'image/webp']
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/notion-types/src/user.ts:
--------------------------------------------------------------------------------
1 | import { type ID } from './core'
2 |
3 | export interface User {
4 | id: ID
5 | version: number
6 | email: string
7 | given_name: string
8 | family_name: string
9 | profile_photo: string
10 | onboarding_completed: boolean
11 | mobile_onboarding_completed: boolean
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@fisch0920/config/tsconfig-react",
3 | "compilerOptions": {
4 | "lib": ["esnext", "ESNext.Array", "dom", "dom.iterable"],
5 | "target": "ES2020",
6 | "jsx": "react-jsx"
7 | },
8 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
9 | "exclude": ["node_modules"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/check.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCheck(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgCheck
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-relation.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/format-date.ts:
--------------------------------------------------------------------------------
1 | export const formatDate = (
2 | input: string | number,
3 | { month = 'short' }: { month?: 'long' | 'short' } = {}
4 | ) => {
5 | const date = new Date(input)
6 | const monthLocale = date.toLocaleString('en-US', { month })
7 | return `${monthLocale} ${date.getUTCDate()}, ${date.getUTCFullYear()}`
8 | }
9 |
--------------------------------------------------------------------------------
/packages/notion-client/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | outDir: 'build',
6 | target: 'node18',
7 | platform: 'node',
8 | format: ['esm'],
9 | splitting: false,
10 | sourcemap: true,
11 | minify: false,
12 | shims: false,
13 | dts: true
14 | })
15 |
--------------------------------------------------------------------------------
/packages/notion-compat/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | outDir: 'build',
6 | target: 'node18',
7 | platform: 'node',
8 | format: ['esm'],
9 | splitting: false,
10 | sourcemap: true,
11 | minify: false,
12 | shims: false,
13 | dts: true
14 | })
15 |
--------------------------------------------------------------------------------
/packages/notion-types/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | outDir: 'build',
6 | target: 'es2018',
7 | platform: 'browser',
8 | format: ['esm'],
9 | splitting: false,
10 | sourcemap: true,
11 | minify: false,
12 | shims: false,
13 | dts: true
14 | })
15 |
--------------------------------------------------------------------------------
/packages/notion-utils/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | outDir: 'build',
6 | target: 'es2018',
7 | platform: 'browser',
8 | format: ['esm'],
9 | splitting: false,
10 | sourcemap: true,
11 | minify: false,
12 | shims: false,
13 | dts: true
14 | })
15 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-relation.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeRelation(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeRelation
12 |
--------------------------------------------------------------------------------
/packages/notion-client/src/types.ts:
--------------------------------------------------------------------------------
1 | import type * as notion from 'notion-types'
2 |
3 | export interface SignedUrlRequest {
4 | permissionRecord: PermissionRecord
5 | url: string
6 | }
7 |
8 | export interface PermissionRecord {
9 | table: string
10 | id: notion.ID
11 | }
12 |
13 | export interface SignedUrlResponse {
14 | signedUrls: string[]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/examples/full/next.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | staticPageGenerationTimeout: 300,
3 | images: {
4 | domains: [
5 | 'www.notion.so',
6 | 'notion.so',
7 | 'images.unsplash.com',
8 | 'abs.twimg.com',
9 | 'pbs.twimg.com',
10 | 's3.us-west-2.amazonaws.com'
11 | ],
12 | formats: ['image/avif', 'image/webp']
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/cra/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/notion-compat/src/convert-color.ts:
--------------------------------------------------------------------------------
1 | import type * as notion from 'notion-types'
2 |
3 | export function convertColor(color: string): notion.Color {
4 | switch (color) {
5 | case 'green':
6 | return 'teal'
7 |
8 | case 'green_background':
9 | return 'teal_background'
10 |
11 | default:
12 | return color as notion.Color
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/chevron-down-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function ChevronDownIcon(props: any) {
4 | const { className, ...rest } = props
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/examples/full/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Head, Html, Main, NextScript } from 'next/document'
2 |
3 | export default class MyDocument extends Document {
4 | override render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/minimal/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Head, Html, Main, NextScript } from 'next/document'
2 |
3 | export default class MyDocument extends Document {
4 | override render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | #### Description
2 |
3 |
6 |
7 | #### Notion Test Page ID
8 |
9 |
16 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | #### Description
2 |
3 |
6 |
7 | #### Notion Test Page ID
8 |
9 |
16 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-select.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeSelect(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeSelect
12 |
--------------------------------------------------------------------------------
/examples/cra/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/normalize-title.ts:
--------------------------------------------------------------------------------
1 | export const normalizeTitle = (title?: string | null): string => {
2 | return (title || '')
3 | .replaceAll(' ', '-')
4 | .replaceAll(
5 | /[^\dA-Za-z\u3000-\u303F\u3041-\u3096\u30A1-\u30FC\u4E00-\u9FFF\uAC00-\uD7AF-]/g,
6 | ''
7 | )
8 | .replaceAll('--', '-')
9 | .replace(/-$/, '')
10 | .replace(/^-/, '')
11 | .trim()
12 | .toLowerCase()
13 | }
14 |
--------------------------------------------------------------------------------
/examples/minimal/lib/config.ts:
--------------------------------------------------------------------------------
1 | // TODO: change this to the notion ID of the page you want to test
2 | export const rootNotionPageId = '067dd719a912471ea9a3ac10710e7fdf'
3 |
4 | // NOTE: rootNotionSpaceId is optional; set it to undefined if you don't want to
5 | // use it.
6 | export const rootNotionSpaceId = 'fde5ac74-eea3-4527-8f00-4482710e1af3'
7 |
8 | export const isDev =
9 | process.env.NODE_ENV === 'development' || !process.env.NODE_ENV
10 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/graceful-image.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Img, type ImgProps } from 'react-image'
3 |
4 | import { isBrowser } from '../utils'
5 |
6 | export function GracefulImage(props: ImgProps) {
7 | if (isBrowser) {
8 | return
9 | } else {
10 | // @ts-expect-error (must use the appropriate subset of props for if using SSR)
11 | return
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/default-page-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function DefaultPageIcon(props: any) {
4 | const { className, ...rest } = props
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # builds
7 | build
8 | dist
9 |
10 | # misc
11 | .DS_Store
12 | .env
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 | .cache
18 | tsconfig.tsbuildinfo
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | .vscode/settings.json
24 |
25 | .next/
26 | .vercel/
27 | .turbo/
28 | .tsimp/
29 |
--------------------------------------------------------------------------------
/examples/full/components/styles.module.css:
--------------------------------------------------------------------------------
1 | @keyframes spinner {
2 | to {
3 | transform: rotate(360deg);
4 | }
5 | }
6 |
7 | .container {
8 | width: 100%;
9 | min-height: 100vh;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | padding: calc(min(2vmin, 24px));
14 | }
15 |
16 | .loadingIcon {
17 | animation: spinner 0.6s linear infinite;
18 | display: block;
19 | width: 24px;
20 | height: 24px;
21 | color: rgba(55, 53, 47, 0.4);
22 | }
23 |
--------------------------------------------------------------------------------
/examples/full/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | // used for rendering equations (optional)
2 | import 'katex/dist/katex.min.css'
3 | // used for code syntax highlighting (optional)
4 | import 'prismjs/themes/prism-tomorrow.css'
5 | // core styles shared by all of react-notion-x (required)
6 | import 'react-notion-x/src/styles.css'
7 | // app styles
8 | import '../styles/globals.css'
9 |
10 | function MyApp({ Component, pageProps }: any) {
11 | return
12 | }
13 |
14 | export default MyApp
15 |
--------------------------------------------------------------------------------
/examples/minimal/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | // core styles shared by all of react-notion-x (required)
2 | import 'react-notion-x/src/styles.css'
3 | import '../styles/globals.css'
4 |
5 | // used for code syntax highlighting (optional)
6 | // import 'prismjs/themes/prism-tomorrow.css'
7 |
8 | // used for rendering equations (optional)
9 | // import 'katex/dist/katex.min.css'
10 |
11 | function MyApp({ Component, pageProps }: any) {
12 | return
13 | }
14 |
15 | export default MyApp
16 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-page-title.ts:
--------------------------------------------------------------------------------
1 | import { type ExtendedRecordMap } from 'notion-types'
2 |
3 | import { getBlockTitle } from './get-block-title'
4 |
5 | export function getPageTitle(recordMap: ExtendedRecordMap) {
6 | const rootBlockId = Object.keys(recordMap.block)[0]
7 | if (!rootBlockId) return null
8 |
9 | const pageBlock = recordMap.block[rootBlockId]?.value
10 |
11 | if (pageBlock) {
12 | return getBlockTitle(pageBlock, recordMap)
13 | }
14 |
15 | return null
16 | }
17 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-board.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCollectionViewBoard(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgCollectionViewBoard
12 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/format-notion-date-time.ts:
--------------------------------------------------------------------------------
1 | import { formatDate } from './format-date'
2 |
3 | export interface NotionDateTime {
4 | type: 'datetime'
5 | start_date: string
6 | start_time?: string
7 | time_zone?: string
8 | }
9 |
10 | export const formatNotionDateTime = (datetime: NotionDateTime) => {
11 | // Adding +00:00 preserve the time in UTC.
12 | const dateString = `${datetime.start_date}T${
13 | datetime.start_time || '00:00'
14 | }+00:00`
15 | return formatDate(dateString)
16 | }
17 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-list.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCollectionViewList(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgCollectionViewList
12 |
--------------------------------------------------------------------------------
/examples/cra/src/index.tsx:
--------------------------------------------------------------------------------
1 | // core styles shared by all of react-notion-x (required)
2 | import 'react-notion-x/src/styles.css'
3 | // used for code syntax highlighting (optional)
4 | // import 'prismjs/themes/prism-tomorrow.css'
5 | // used for rendering equations (optional)
6 | // import 'katex/dist/katex.min.css'
7 | import './index.css'
8 |
9 | import { createRoot } from 'react-dom/client'
10 |
11 | import App from './App'
12 |
13 | const root = createRoot(document.getElementById('root')!)
14 | root.render( )
15 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/file-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function FileIcon(props: any) {
4 | const { className, ...rest } = props
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/clear-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { cs } from '../utils'
4 |
5 | export function ClearIcon(props: any) {
6 | const { className, ...rest } = props
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-multi-select.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeMultiSelect(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeMultiSelect
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-gallery.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCollectionViewGallery(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgCollectionViewGallery
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-date.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeDate(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeDate
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/readme.md:
--------------------------------------------------------------------------------
1 | # react-notion-x third-party modules
2 |
3 | All of the modules in this folder contain large, optional, third-party dependencies.
4 |
5 | To guarantee a minimal bundle size, they must be imported separately from the rest of `react-notion-x`.
6 |
7 | ## License
8 |
9 | MIT © [Travis Fischer](https://transitivebullsh.it)
10 |
11 | Support my OSS work by following me on twitter
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-select.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-date-value.ts:
--------------------------------------------------------------------------------
1 | import type * as types from 'notion-types'
2 |
3 | /**
4 | * Attempts to find a valid date from a given property.
5 | */
6 | export const getDateValue = (prop: any[]): types.FormattedDate | null => {
7 | if (prop && Array.isArray(prop)) {
8 | if (prop[0] === 'd') {
9 | return prop[1] as types.FormattedDate
10 | } else {
11 | for (const v of prop) {
12 | const value = getDateValue(v as any[])
13 | if (value) {
14 | return value
15 | }
16 | }
17 | }
18 | }
19 |
20 | return null
21 | }
22 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | workflow_dispatch:
8 |
9 | jobs:
10 | release:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: write
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 | - uses: pnpm/action-setup@v4
19 | - uses: actions/setup-node@v4
20 | with:
21 | node-version: lts/*
22 | cache: pnpm
23 | - run: pnpm dlx changelogithub
24 | env:
25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-table.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCollectionViewTable(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgCollectionViewTable
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup'
2 |
3 | export default defineConfig({
4 | entry: [
5 | 'src/index.ts',
6 | 'src/third-party/code.tsx',
7 | 'src/third-party/collection.tsx',
8 | 'src/third-party/equation.tsx',
9 | 'src/third-party/modal.tsx',
10 | 'src/third-party/pdf.tsx'
11 | ],
12 | outDir: 'build',
13 | target: 'es2018',
14 | platform: 'browser',
15 | format: ['esm'],
16 | splitting: false,
17 | shims: false,
18 | dts: true,
19 | minify: false,
20 | sourcemap: true,
21 | external: ['react-pdf', 'react', 'react-dom']
22 | })
23 |
--------------------------------------------------------------------------------
/examples/cra/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-text.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeText(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeText
12 |
--------------------------------------------------------------------------------
/examples/minimal/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-x-example-minimal",
3 | "version": "7.7.3",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "deploy": "vercel deploy",
11 | "test": "run-s test:*",
12 | "test:lint": "eslint ."
13 | },
14 | "dependencies": {
15 | "next": "catalog:",
16 | "notion-client": "workspace:*",
17 | "notion-types": "workspace:*",
18 | "notion-utils": "workspace:*",
19 | "react": "catalog:",
20 | "react-dom": "catalog:",
21 | "react-notion-x": "workspace:*"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-person-2.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypePerson2(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypePerson2
12 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-page-tweet-ids.ts:
--------------------------------------------------------------------------------
1 | import type * as types from 'notion-types'
2 |
3 | import { getPageTweetUrls } from './get-page-tweet-urls'
4 |
5 | /**
6 | * Gets the IDs of all tweets embedded on a page.
7 | */
8 | export const getPageTweetIds = (
9 | recordMap: types.ExtendedRecordMap
10 | ): string[] => {
11 | const tweetUrls = getPageTweetUrls(recordMap)
12 | return tweetUrls
13 | .map((url) => {
14 | try {
15 | const u = new URL(url)
16 | const parts = u.pathname.split('/')
17 | return parts.at(-1)
18 | } catch {
19 | return
20 | }
21 | })
22 | .filter(Boolean)
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | import CheckIcon from '../icons/check'
4 |
5 | export function Checkbox({
6 | isChecked
7 | }: {
8 | isChecked: boolean
9 | blockId?: string
10 | }) {
11 | let content = null
12 |
13 | if (isChecked) {
14 | content = (
15 |
16 |
17 |
18 | )
19 | } else {
20 | content =
21 | }
22 |
23 | return (
24 | {content}
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-board.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/collection-column-title.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import { type CollectionPropertySchema } from 'notion-types'
3 |
4 | import { PropertyIcon } from '../icons/property-icon'
5 |
6 | export function CollectionColumnTitle({
7 | schema
8 | }: {
9 | schema: CollectionPropertySchema
10 | }) {
11 | return (
12 |
13 |
17 |
18 |
{schema.name}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-list.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-number.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeNumber(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeNumber
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-text-content.ts:
--------------------------------------------------------------------------------
1 | import type * as types from 'notion-types'
2 |
3 | /**
4 | * Gets the raw, unformatted text content of a block's content value.
5 | *
6 | * This is useful, for instance, for extracting a block's `title` without any
7 | * rich text formatting.
8 | */
9 | export const getTextContent = (text?: types.Decoration[]): string => {
10 | if (!text) {
11 | return ''
12 | } else if (Array.isArray(text)) {
13 | return (
14 | text?.reduce(
15 | (prev, current) =>
16 | prev + (current[0] !== '⁍' && current[0] !== '‣' ? current[0] : ''),
17 | ''
18 | ) ?? ''
19 | )
20 | } else {
21 | return text
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-checkbox.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeCheckbox(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeCheckbox
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-timestamp.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeTimestamp(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeTimestamp
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-gallery.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-page-tweet-urls.ts:
--------------------------------------------------------------------------------
1 | import type * as types from 'notion-types'
2 |
3 | /**
4 | * Gets the URLs of all tweets embedded on a page.
5 | */
6 | export const getPageTweetUrls = (
7 | recordMap: types.ExtendedRecordMap
8 | ): string[] => {
9 | const blockIds = Object.keys(recordMap.block)
10 | const tweetUrls: string[] = blockIds
11 | .map((blockId) => {
12 | const block = recordMap.block[blockId]?.value
13 |
14 | if (block?.type === 'tweet') {
15 | const tweetUrl = block.properties?.source?.[0]?.[0]
16 |
17 | if (tweetUrl) {
18 | return tweetUrl
19 | }
20 | }
21 | })
22 | .filter(Boolean)
23 |
24 | return Array.from(new Set(tweetUrls))
25 | }
26 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-formula.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeFormula(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeFormula
12 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-d4102177-3cb3-4d36-8f39-92d84147f825.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "06e60f3a-dd9c-4934-986b-68c18987dac1",
5 | "bade9948-e8d0-4706-b135-f4ac402f363c",
6 | "20e1fdd9-312b-466d-a1e9-96b88652fd7a",
7 | "1d2cf72f-e1cc-4a26-bfd8-d37598b6bfa0",
8 | "0cf08062-1177-4438-95ca-f08ffa08f68b",
9 | "b1ed6778-ccb0-461e-917c-ddd64e0af148",
10 | "2cc221c5-bad1-4d70-8fca-6dbaa28ef17d",
11 | "db375bde-34fb-44ea-a3f2-75e41fa92e93",
12 | "203c29af-2b16-43cb-a846-db74eced0a3f",
13 | "bd458279-307c-4a15-8838-42e6cfa7ffee",
14 | "49b2b551-8b85-4bdb-bd0b-484a068b29c3"
15 | ],
16 | "aggregationResults": [],
17 | "total": 11
18 | }
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-url.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeUrl(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeUrl
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/link-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function LinkIcon(props: any) {
4 | const { className, ...rest } = props
5 | return (
6 |
13 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/__snapshots__/normalize-url.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`normalizeUrl valid 1`] = `"test.com"`;
4 |
5 | exports[`normalizeUrl valid 2`] = `"test.com/123"`;
6 |
7 | exports[`normalizeUrl valid 3`] = `"test.com"`;
8 |
9 | exports[`normalizeUrl valid 4`] = `"test.com"`;
10 |
11 | exports[`normalizeUrl valid 5`] = `"test.com"`;
12 |
13 | exports[`normalizeUrl valid 6`] = `"test.com/foo/bar"`;
14 |
15 | exports[`normalizeUrl valid 7`] = `"test.com/foo/bar"`;
16 |
17 | exports[`normalizeUrl valid 8`] = `"test.com/foo/bar"`;
18 |
19 | exports[`normalizeUrl valid 9`] = `"notion.so/image/s3.us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fae16c287-668f-4ea7-90a8-5ed96302e14f%2Fquilt-opt.jpg"`;
20 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-block-collection-id.ts:
--------------------------------------------------------------------------------
1 | import { type Block, type ExtendedRecordMap } from 'notion-types'
2 |
3 | export function getBlockCollectionId(
4 | block: Block,
5 | recordMap: ExtendedRecordMap
6 | ): string | null {
7 | const collectionId =
8 | (block as any).collection_id ||
9 | (block as any).format?.collection_pointer?.id
10 |
11 | if (collectionId) {
12 | return collectionId
13 | }
14 |
15 | const collectionViewId = (block as any)?.view_ids?.[0]
16 | if (collectionViewId) {
17 | const collectionView = recordMap.collection_view?.[collectionViewId]?.value
18 | if (collectionView) {
19 | const collectionId = collectionView.format?.collection_pointer?.id
20 | return collectionId
21 | }
22 | }
23 |
24 | return null
25 | }
26 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-person-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-file.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeFile(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeFile
12 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { config } from '@fisch0920/config/eslint'
2 |
3 | export default [
4 | ...config,
5 | {
6 | ignores: ['.snapshots/', '.next/', '.vercel/', 'build/', 'docs/']
7 | },
8 | {
9 | files: ['**/*.ts', '**/*.tsx'],
10 | rules: {
11 | 'react/prop-types': 'off',
12 | 'unicorn/no-array-reduce': 'off',
13 | 'unicorn/prefer-global-this': 'off',
14 | 'unicorn/filename-case': 'off',
15 | 'no-process-env': 'off',
16 | 'array-callback-return': 'off',
17 | 'jsx-a11y/click-events-have-key-events': 'off',
18 | 'jsx-a11y/no-static-element-interactions': 'off',
19 | 'jsx-a11y/media-has-caption': 'off',
20 | 'jsx-a11y/interactive-supports-focus': 'off',
21 | '@typescript-eslint/naming-convention': 'off'
22 | }
23 | }
24 | ]
25 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-phone-number.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypePhoneNumber(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypePhoneNumber
12 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-block-icon.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Block,
3 | type ExtendedRecordMap,
4 | type PageBlock
5 | } from 'notion-types'
6 |
7 | import { getBlockCollectionId } from './get-block-collection-id'
8 |
9 | export function getBlockIcon(block: Block, recordMap: ExtendedRecordMap) {
10 | if ((block as PageBlock).format?.page_icon) {
11 | return (block as PageBlock).format?.page_icon
12 | }
13 |
14 | if (
15 | block.type === 'collection_view_page' ||
16 | block.type === 'collection_view'
17 | ) {
18 | const collectionId = getBlockCollectionId(block, recordMap)
19 | if (collectionId) {
20 | const collection = recordMap.collection[collectionId]?.value
21 |
22 | if (collection) {
23 | return collection.icon
24 | }
25 | }
26 | }
27 |
28 | return null
29 | }
30 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-1a1863bd-f819-41dc-a228-831ca7a75d2a.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "5bdac465-4c43-4d58-88be-5f83a2ab2f43",
5 | "d389225d-1ed7-4234-a83d-85eb3ed46c33",
6 | "9afef1ef-b1eb-4afa-bc0a-a529bd0980e5",
7 | "4fe48fff-7247-4872-9df0-0ae1bd512e7b",
8 | "9fed1e10-4d32-4482-bb8a-7332b87b794f",
9 | "5994f819-887e-4e3e-b7dc-0095f0b4df7d",
10 | "f4edd0e8-9106-46e1-a983-a390ce26a67c",
11 | "d5747412-75f3-43c2-9dd2-e82956c0f11c",
12 | "053abff1-2986-4ff9-ba0d-dc3d9eca8a73",
13 | "f99806d7-bcd6-4c68-9084-bb60e317e3a7",
14 | "5049a1e2-4929-46f6-9fed-f36956469df6",
15 | "6cb98fa9-927a-42db-a7df-665020bcb456",
16 | "2758897b-91e1-41f1-8360-ad7872e8c15b"
17 | ],
18 | "aggregationResults": [],
19 | "total": 13
20 | }
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-date.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/normalize-url.ts:
--------------------------------------------------------------------------------
1 | import memoize from 'memoize'
2 | import normalizeUrlImpl from 'normalize-url'
3 |
4 | export const normalizeUrl = memoize((url?: string) => {
5 | if (!url) {
6 | return ''
7 | }
8 |
9 | try {
10 | if (url.startsWith('https://www.notion.so/image/')) {
11 | const u = new URL(url)
12 | const subUrl = decodeURIComponent(u.pathname.slice('/image/'.length))
13 | const normalizedSubUrl = normalizeUrl(subUrl)
14 | u.pathname = `/image/${encodeURIComponent(normalizedSubUrl)}`
15 | url = u.toString()
16 | }
17 |
18 | return normalizeUrlImpl(url, {
19 | stripProtocol: true,
20 | stripWWW: true,
21 | stripHash: true,
22 | stripTextFragment: true,
23 | removeQueryParameters: true
24 | })
25 | } catch {
26 | return ''
27 | }
28 | })
29 |
--------------------------------------------------------------------------------
/packages/notion-types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-types",
3 | "version": "7.7.3",
4 | "type": "module",
5 | "description": "TypeScript types for core Notion data structures.",
6 | "repository": "NotionX/react-notion-x",
7 | "author": "Travis Fischer ",
8 | "license": "MIT",
9 | "main": "./build/index.js",
10 | "module": "./build/index.js",
11 | "types": "./build/index.d.ts",
12 | "sideEffects": false,
13 | "files": [
14 | "build"
15 | ],
16 | "engines": {
17 | "node": ">=18"
18 | },
19 | "scripts": {
20 | "build": "tsup",
21 | "dev": "tsup --watch",
22 | "watch": "tsup --watch --silent --onSuccess 'echo build successful'",
23 | "clean": "del build",
24 | "test": "run-s test:*",
25 | "test:lint": "eslint .",
26 | "test:typecheck": "tsc --noEmit"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-block-title.ts:
--------------------------------------------------------------------------------
1 | import { type Block, type ExtendedRecordMap } from 'notion-types'
2 |
3 | import { getBlockCollectionId } from './get-block-collection-id'
4 | import { getTextContent } from './get-text-content'
5 |
6 | export function getBlockTitle(block: Block, recordMap: ExtendedRecordMap) {
7 | if (block.properties?.title) {
8 | return getTextContent(block.properties.title)
9 | }
10 |
11 | if (
12 | block.type === 'collection_view_page' ||
13 | block.type === 'collection_view'
14 | ) {
15 | const collectionId = getBlockCollectionId(block, recordMap)
16 |
17 | if (collectionId) {
18 | const collection = recordMap.collection[collectionId]?.value
19 |
20 | if (collection) {
21 | return getTextContent(collection.name)
22 | }
23 | }
24 | }
25 |
26 | return ''
27 | }
28 |
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/signed-urls.json:
--------------------------------------------------------------------------------
1 | {
2 | "signedUrls": [
3 | "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/1288fa7f-5aca-4b01-9f83-be0db99f5a86/The-Growth-Handbook-by-Intercom.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20200816%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200816T021959Z&X-Amz-Expires=86400&X-Amz-Signature=27f058c2436e2e9e4ae3698dca16936c0e4b562c923700eff3fe7719ce409918&X-Amz-SignedHeaders=host",
4 | "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/9c998f9f-4808-4e9f-ab35-55b25db0fd3f/example.mp3?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20200816%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20200816T021959Z&X-Amz-Expires=86400&X-Amz-Signature=c604adcc2675afeac234e6f7a5bac3de80a35ae43773417f924651112ff52ff6&X-Amz-SignedHeaders=host"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/examples/minimal/components/NotionPage.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import { type ExtendedRecordMap } from 'notion-types'
3 | import { getPageTitle } from 'notion-utils'
4 | import { NotionRenderer } from 'react-notion-x'
5 |
6 | export function NotionPage({
7 | recordMap,
8 | rootPageId
9 | }: {
10 | recordMap: ExtendedRecordMap
11 | rootPageId?: string
12 | }) {
13 | if (!recordMap) {
14 | return null
15 | }
16 |
17 | const title = getPageTitle(recordMap)
18 |
19 | return (
20 | <>
21 |
22 |
23 |
24 | {title}
25 |
26 |
27 |
33 | >
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/pdf.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Document, Page, pdfjs } from 'react-pdf'
3 |
4 | // ensure pdfjs can find its worker script regardless of how react-notion-x is bundled
5 | pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`
6 |
7 | export function Pdf({ file, ...rest }: { file: string }) {
8 | const [numPages, setNumPages] = React.useState(0)
9 |
10 | function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
11 | setNumPages(numPages)
12 | }
13 |
14 | return (
15 |
16 | {Array.from(Array.from({ length: numPages }), (_, index) => (
17 |
18 | ))}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/audio.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import { type AudioBlock } from 'notion-types'
3 |
4 | import { useNotionContext } from '../context'
5 | import { cs } from '../utils'
6 |
7 | export function Audio({
8 | block,
9 | className
10 | }: {
11 | block: AudioBlock
12 | className?: string
13 | }) {
14 | const { recordMap } = useNotionContext()
15 |
16 | let source =
17 | recordMap.signed_urls[block.id] || block.properties?.source?.[0]?.[0]
18 |
19 | if (!source) {
20 | return null
21 | }
22 |
23 | if (block.space_id) {
24 | const url = new URL(source)
25 | url.searchParams.set('spaceId', block.space_id)
26 | source = url.toString()
27 | }
28 |
29 | return (
30 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-9a43758f-21c5-4fc2-a4b5-0fc82971b79f.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "3cf9537e-e47c-456a-9cfb-1f6fbecc49b6",
5 | "3a6b159d-905d-4d63-a623-e85ad03ce964",
6 | "6d1d75e9-3195-45d2-9016-6a14f3882ef4",
7 | "5a380378-ae79-4be5-b887-dbda78444d9d",
8 | "f8fcb6f0-1d50-4de7-a0f2-040c1d8d295b",
9 | "f16c45fe-f2c8-45a7-b181-d64605bd5840",
10 | "b27d3c7b-dfaa-4ca3-8a58-76a71d4e1249",
11 | "28cf85e8-77d6-4ab2-8ce0-b8879dcf2525",
12 | "1893193b-bdb6-4531-9fee-d9f4405e94df",
13 | "8c4ed0e2-57f4-44c0-908c-45118a267a8c",
14 | "abb057ce-1ce2-4534-b5dc-dd7abc27fa72",
15 | "081f52dc-abad-4119-b79e-94b155327d62",
16 | "781dbbf8-028b-401f-912a-ec0984a7e884",
17 | "0a9655db-94cf-4b07-8414-18b2c12b1f88"
18 | ],
19 | "aggregationResults": [],
20 | "total": 14
21 | }
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | test:
7 | name: Test Node.js ${{ matrix.node-version }}
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | fail-fast: true
12 | matrix:
13 | node-version:
14 | - 22
15 | - 23
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - uses: pnpm/action-setup@v4
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | cache: 'pnpm'
24 |
25 | - run: pnpm install --frozen-lockfile --strict-peer-dependencies
26 | - run: pnpm build
27 | - run: pnpm test
28 |
29 | - name: Build minimal example
30 | run: |
31 | cd examples/minimal
32 | pnpm build
33 |
34 | - name: Build full example
35 | run: |
36 | cd examples/full
37 | pnpm build
38 |
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/board-results.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "value": {
4 | "type": "select"
5 | },
6 | "blockIds": ["c755e6c7-dcdd-433b-9c4c-ac9b3119ae92"],
7 | "total": 1,
8 | "aggregationResult": {
9 | "type": "number",
10 | "value": 1
11 | }
12 | },
13 | {
14 | "value": {
15 | "type": "select",
16 | "value": "foo"
17 | },
18 | "blockIds": ["43fb2254-b7e3-412c-a461-566f7a83918e"],
19 | "total": 1,
20 | "aggregationResult": {
21 | "type": "number",
22 | "value": 1
23 | }
24 | },
25 | {
26 | "value": {
27 | "type": "select",
28 | "value": "bar"
29 | },
30 | "blockIds": [
31 | "3b506960-ea91-4b59-8e7c-f3c51e40529a",
32 | "3fce55d4-2528-4d05-8af5-6f2bbc4af418"
33 | ],
34 | "total": 2,
35 | "aggregationResult": {
36 | "type": "number",
37 | "value": 2
38 | }
39 | }
40 | ]
41 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/e777a528-9404-4e96-9f26-0014be705592-c01c2f48-5442-47d8-adb3-01c4f8bb8e58.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "274924c4-c151-4e45-9ac4-fd3b8839a56c",
5 | "a5b2ed08-4322-4923-888f-c70193751759",
6 | "7c579637-e84f-4ddf-ab72-78a0d414baf8",
7 | "e075359b-b750-46c4-86a1-1e76027e140f",
8 | "bfc372e8-125f-4392-91b2-004532d1d808",
9 | "f6c7514f-b3bf-4a1b-99fd-8588eb0fdc13",
10 | "db15dad7-0fda-4900-9c68-d2a4a99e362a",
11 | "62b3eea9-3084-461d-bbc1-c727360218ad",
12 | "20a58853-197a-4909-8fb7-900b76cba9c3",
13 | "71d3bf63-2eea-464f-aa6c-ccd52ba13856",
14 | "f2a7b5f6-7cf5-4c14-bf61-597f45870469",
15 | "ef225d59-62fe-4e8c-bf78-01000aec6466",
16 | "e355c83e-efd6-43a9-9fbd-b8c863ce28aa",
17 | "14996a91-fff0-4234-bc9b-64250b59ae31",
18 | "ceae18ce-490a-464a-ac7c-822df32c9a28"
19 | ],
20 | "aggregationResults": [],
21 | "total": 15
22 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/e777a528-9404-4e96-9f26-0014be705592-c0d2d91c-e2a5-4ea5-a9b7-2b2d8ae2d591.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "841918aa-f2a3-4d4c-b5ad-64b0f57c47b8",
5 | "e722d82d-e545-4dc5-9658-c66260f0d3f4",
6 | "282f1ce5-85cc-47c3-8145-31ebee469aa4",
7 | "dddd453b-579c-472a-9339-26dcfcaa1976",
8 | "3cbd18cd-cab2-4b10-9b0d-e67ff1e365e1",
9 | "afeaba17-8f70-40c9-a648-4e0afc314948",
10 | "73447b75-434c-4eee-a68d-f6cecb8a844c",
11 | "414ba2c7-d02a-4f5d-acc3-5512ff3f6b35",
12 | "95c79219-0f51-43ad-895e-53f8eb08ee93",
13 | "f2379c53-d112-42a8-a248-fabebaa98337",
14 | "e650d5c6-66c0-44d4-80da-76da908ca284",
15 | "2ed75575-6e95-4a55-9fcc-01853125be21",
16 | "001b1958-7c2d-4a11-8f9d-ce1c0fe476a9",
17 | "9e5ce76a-7550-449f-a7ed-4ade717ab7a7",
18 | "7611bc05-d4c6-41de-9a18-f9a1071d99c8"
19 | ],
20 | "aggregationResults": [],
21 | "total": 15
22 | }
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/copy.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCopy(props: React.SVGProps) {
4 | return (
5 |
12 |
16 |
20 |
21 | )
22 | }
23 |
24 | export default SvgCopy
25 |
--------------------------------------------------------------------------------
/examples/cra/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { type ExtendedRecordMap } from 'notion-types'
2 | import { NotionRenderer } from 'react-notion-x'
3 | import { Code } from 'react-notion-x/build/third-party/code'
4 | import { Collection } from 'react-notion-x/build/third-party/collection'
5 | import { Equation } from 'react-notion-x/build/third-party/equation'
6 | import { Modal } from 'react-notion-x/build/third-party/modal'
7 | import { Pdf } from 'react-notion-x/build/third-party/pdf'
8 |
9 | import defaultRecordMap from './record-map.json'
10 |
11 | function App() {
12 | const recordMap = defaultRecordMap as unknown as ExtendedRecordMap
13 |
14 | return (
15 |
27 | )
28 | }
29 |
30 | export default App
31 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/collections.md:
--------------------------------------------------------------------------------
1 | # Collection Notes
2 |
3 | ## TODO
4 |
5 | - support row popup
6 | - support row popup and selected view URL params
7 | - `?v=` and `?p=`
8 | - board and table views scrollable but not necessarily 100% width
9 | - test hiding various properties
10 | - test more advanced collection views
11 |
12 | - hash block id in URL should highlight that block
13 | - iframe asset lazy loading show spinner
14 | - some notion users in collections aren't loaded up front
15 | - toggle should remember state in local storage
16 | - for bookmarks, icons, and other image assets, gracefully handle loading errors
17 |
18 | ### Card
19 |
20 | - `coverType: files`
21 | - for gallery view and board view
22 | - `coverType: page_content`
23 | - if no images found, fallback to a basic view of the page's content
24 | - https://www.notion.so/Group-1-eba6e579e7544a1bb93a3f4a77b4c01e
25 |
26 | ### Calendar View
27 |
28 | - TODO
29 |
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/blocks/audio.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "4f2d754d-89a5-44da-a84c-e8753cceb8b2",
3 | "version": 7,
4 | "type": "audio",
5 | "properties": {
6 | "source": [
7 | [
8 | "https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9c998f9f-4808-4e9f-ab35-55b25db0fd3f/example.mp3"
9 | ]
10 | ]
11 | },
12 | "created_time": 1597765156711,
13 | "last_edited_time": 1597765140000,
14 | "parent_id": "34d650c6-5da3-4f88-8335-dbd3ddd141dc",
15 | "parent_table": "block",
16 | "alive": true,
17 | "copied_from": "27cefd04-ba63-4c4d-8a78-565afb47214b",
18 | "file_ids": ["9c998f9f-4808-4e9f-ab35-55b25db0fd3f"],
19 | "created_by_table": "notion_user",
20 | "created_by_id": "db401f86-4012-4d18-9445-236978ef32df",
21 | "last_edited_by_table": "notion_user",
22 | "last_edited_by_id": "db401f86-4012-4d18-9445-236978ef32df",
23 | "shard_id": 924403,
24 | "space_id": "fde5ac74-eea3-4527-8f00-4482710e1af3"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/parse-page-id.ts:
--------------------------------------------------------------------------------
1 | import { idToUuid } from './id-to-uuid'
2 |
3 | const pageIdRe = /\b([\da-f]{32})\b/
4 |
5 | // TODO
6 | // eslint-disable-next-line security/detect-unsafe-regex
7 | const pageId2Re = /\b([\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})\b/
8 |
9 | /**
10 | * Robustly extracts the notion page ID from a notion URL or pathname suffix.
11 | *
12 | * Defaults to returning a UUID (with dashes).
13 | */
14 | export const parsePageId = (
15 | id: string | undefined | null = '',
16 | { uuid = true }: { uuid?: boolean } = {}
17 | ): string | undefined => {
18 | if (!id) return
19 |
20 | id = id.split('?')[0]!
21 | if (!id) return
22 |
23 | const match = id.match(pageIdRe)
24 |
25 | if (match) {
26 | return uuid ? idToUuid(match[1]) : match[1]
27 | }
28 |
29 | const match2 = id.match(pageId2Re)
30 | if (match2) {
31 | return uuid ? match2[1] : match2[1]!.replaceAll('-', '')
32 | }
33 |
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/empty-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function EmptyIcon(props: any) {
4 | const { className, ...rest } = props
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/packages/notion-types/src/api.ts:
--------------------------------------------------------------------------------
1 | import { type RecordMap } from './maps'
2 |
3 | // API types
4 | // ----------------------------------------------------------------------------
5 |
6 | export interface RecordValues {
7 | results: T[]
8 | }
9 |
10 | export interface SearchParams {
11 | ancestorId: string
12 | query: string
13 | filters?: {
14 | isDeletedOnly: boolean
15 | excludeTemplates: boolean
16 | isNavigableOnly: boolean
17 | requireEditPermissions: boolean
18 | }
19 | limit?: number
20 | searchSessionId?: string
21 | }
22 |
23 | export interface SearchResults {
24 | recordMap: RecordMap
25 | results: SearchResult[]
26 | total: number
27 | }
28 |
29 | export interface SearchResult {
30 | id: string
31 | isNavigable: boolean
32 | score: number
33 | highlight: {
34 | pathText: string
35 | text: string
36 | }
37 | }
38 |
39 | export interface APIError {
40 | errorId: string
41 | name: string
42 | message: string
43 | }
44 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-048d8535-b957-488a-89f0-ade8d3c4c026.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "08ca98f2-bc53-487f-8185-d250ca885632",
5 | "efdf0752-0130-49a1-a9f2-e4b21b0e720e",
6 | "e7190a3a-132e-48a2-8660-e7df6da0a1d5",
7 | "f44230a0-9778-4e33-a586-bc78afb659b4",
8 | "d045ec1f-c1df-48c7-93c9-b7c07dfa55ae",
9 | "e8bb9f90-8d11-4d4c-bf02-50be069d3497",
10 | "c291c341-6270-4429-911a-712692a7f27a",
11 | "bbaed06c-3a14-42b8-964b-438f1fbacb7d",
12 | "397a5b0a-78b6-4d44-98a9-3951d525611f",
13 | "ed72b903-be42-4f83-bb75-688d6bfddf11",
14 | "1ba167a8-2d0a-40fc-a5c7-f62b8858e715",
15 | "edfbad32-801d-4ce4-ae61-fb5cb0240ff3",
16 | "0917ae4f-d544-4e50-8b56-0d41ebb1d3ee",
17 | "effaf7b4-d44f-4f76-87fb-ddb55e4f235d",
18 | "1e03f644-396f-42ba-addb-3b1dbf2a513c",
19 | "bad715e5-9ed4-4105-894a-54dbb9cb6731"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-59a7246f-e923-4faf-9246-75851294365b.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e744573e-3e3e-4d72-bf70-abc408a988f7",
5 | "5ed8919b-a754-4695-a071-afffff2470b9",
6 | "041482e3-a3a4-41c4-a30d-38f1621a45f8",
7 | "4637d950-fddd-46ff-831e-9641df3253cd",
8 | "7be223f5-d93c-4536-b450-bcff98b777c5",
9 | "8a9f4000-5840-4726-b31d-566ba40d3f44",
10 | "93ee0f76-145b-4283-906b-bc6e1f9d74cc",
11 | "cc7e9314-8d65-45f4-9f9b-f71626c610ba",
12 | "a26cf209-469d-42db-a726-c9d7ec8f809a",
13 | "8c4ed0e2-57f4-44c0-908c-45118a267a8c",
14 | "1b3f747d-80d4-484c-90e1-e1d56b7bd3f6",
15 | "28141942-a4be-4bab-a43b-da51ff36d8b3",
16 | "9cc65a75-21a0-4c68-a6bf-da9291bde225",
17 | "17ce2264-a0bc-4cd7-a1b4-4c4fe0dfc746",
18 | "bfd3f7a1-c119-45d2-acd3-77f5eea0e16f",
19 | "9ef5a892-bb30-4ce2-a124-af0a4ce308ee"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-5ae81e49-33ad-47bf-9c14-14adb9145b4d.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "81e021ae-d175-4d6c-aeeb-c934ac0b3ac6",
5 | "caed86cc-1745-4629-bc35-6387d012efc4",
6 | "a653ae64-a4d3-464e-bf8c-9f5acf9eeb97",
7 | "bf120f14-3973-44be-aa6e-57e9fd7f99f0",
8 | "28cf85e8-77d6-4ab2-8ce0-b8879dcf2525",
9 | "fac1d890-740a-4c57-bfd6-4612ec1e18cc",
10 | "02bbf5ab-e2c7-48f5-b782-145a5f1551e5",
11 | "5d07bfbd-fd51-4cab-867b-39d67061c193",
12 | "5049a1e2-4929-46f6-9fed-f36956469df6",
13 | "4fc14aba-c8c2-4fc6-ab5a-706f9e5722b9",
14 | "55e81f47-1606-46be-8164-ca014eeca0f1",
15 | "c3d50be3-8ba7-4a1b-a341-cc096ed6130e",
16 | "203c29af-2b16-43cb-a846-db74eced0a3f",
17 | "6c670f9d-c383-46c8-8bf8-6b9998d8c93b",
18 | "58cc0600-d996-4946-b575-8d1891efba85",
19 | "9a266d39-ce62-49b0-8715-84afb16f6a41"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-693b4304-0bbd-4f9a-85f1-7c9cb1768110.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "14861372-4966-4bd2-9241-15eb54af1740",
5 | "5a380378-ae79-4be5-b887-dbda78444d9d",
6 | "b377bc3f-7e1a-438f-8a51-39ba81453826",
7 | "df11c1c9-550e-4b70-817f-b4762579fb7d",
8 | "c61dd34c-5c34-4f5c-bec5-f16ed5b89cb0",
9 | "fb6f0fae-aa51-423b-8118-655f91b2a171",
10 | "3cb90950-bff5-4557-951a-090990a2c8f9",
11 | "f006a1e4-3bdb-4882-a640-db64b88f00c0",
12 | "139ac7d7-30d5-4b5e-9393-7a282d8ff635",
13 | "21ba0c99-00bd-4ff2-b3bb-ddbebdda399d",
14 | "3aeabcf7-fb3e-4849-b1b0-72072f05e2cb",
15 | "e9ba7138-c112-4ffc-9a49-58cc79ca97ba",
16 | "34dd9fa6-aa50-456d-bb01-971c18c127fc",
17 | "8f01a708-e394-40c0-8c97-48c93124fe27",
18 | "8c33dcb8-abfd-480a-85fb-06b75799b9f2",
19 | "73e3c5c0-0dc9-4cbe-b7fe-afa40d33a550"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-c69e1799-88cb-4135-a10d-1ec4617f0aa6.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "c7ea3f1d-91de-451d-b271-271865442cc4",
5 | "e79b8489-eca3-4c8e-ae2e-97fc6aa1f42a",
6 | "64423602-a7b7-49f8-b986-6e26ec296044",
7 | "7ed2188a-e376-45ad-b032-ce4b72129c6d",
8 | "dfd8bd11-873c-4778-8e08-02e5e6b3b6a4",
9 | "6160cdcc-b421-4bb8-b65d-2b47a89f59af",
10 | "18155006-506b-4529-9ef2-0cd45a8426ac",
11 | "f76af635-59d6-4321-a1a5-254314c82abf",
12 | "d29887cc-0496-4f13-9780-2dec88465fd3",
13 | "2c3c06fb-ba3d-4a58-8eba-4097a9011961",
14 | "5ef2821b-a547-45d8-b1ba-fcebe8adacc1",
15 | "4721be6b-2e37-484e-b7ab-78eda2ac1fc8",
16 | "af144d64-bac6-47ad-afea-5353bd445047",
17 | "1f0cc211-400a-4a31-bc20-ede8a7ee06ad",
18 | "41edbc2a-e801-47df-8f51-7b26cfae051b",
19 | "8b9c71b6-4a6c-4075-87a4-6878d3bdcb9f"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-cfe27a86-00d0-4322-b2db-2bc3e69269e2.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "6e391c53-f569-4ecc-9447-45c3b5e81205",
5 | "20e1fdd9-312b-466d-a1e9-96b88652fd7a",
6 | "1d2cf72f-e1cc-4a26-bfd8-d37598b6bfa0",
7 | "37532462-8d6d-4ffc-bea8-98b6643ee2e3",
8 | "92079ec1-3110-4cd7-8ffa-e8500d5850d9",
9 | "d4e42ee3-b07e-4f33-985d-72722f80714c",
10 | "849aca7d-95ff-4a55-993b-545fbb024e9f",
11 | "c8e4d0dd-a27d-4798-bb58-531102e03706",
12 | "ac6f5d8c-83b8-40de-bd28-8f7fc4c0ef34",
13 | "7f4b610d-8ccc-4a6e-859a-9af750ef197c",
14 | "1f9fa735-a6ff-481c-aa72-0d824660dd36",
15 | "3ef4ffb1-40dd-4c0d-9094-37c23c181c5f",
16 | "9e01bab7-4ea9-4cc8-aca3-4b5588c9748b",
17 | "ae9380a8-cd3f-407b-9547-969b65e0a936",
18 | "02f0e434-bc26-41d3-85b6-3763ebf5508d",
19 | "9c709e5e-f447-4c97-94e4-8abd9c61fb8b"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-d8f02741-e03a-4e40-91d7-0b821ee4014d.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "dfc56d92-0eb1-4da1-a9c4-7079b85a65c3",
5 | "d580b754-59c2-4d78-9d6b-85d50ef5a343",
6 | "9c196736-4326-44e9-b2b4-0bb3ec87a7f4",
7 | "03bc6e86-ad24-4535-8b67-bedf868ec936",
8 | "0dbd808e-acde-46af-b341-c509726914bb",
9 | "5b33f792-195a-44d2-9bd6-f6738ff1f432",
10 | "1a04ee78-9062-4483-92f9-a812dca78f08",
11 | "43f6ad58-8cc3-4875-8476-e8856727056c",
12 | "badb5fbc-a2a3-4f65-af67-1a85e1acb1d9",
13 | "0f6bf1b5-3152-4620-9ca6-c70cc9220146",
14 | "26541856-88cd-47bb-9df1-af7ff478859d",
15 | "95dd3862-b518-4055-85f2-40a89c0555c0",
16 | "6eaab6b4-38bb-4f5b-bdd0-24f53f791cd6",
17 | "4716d21c-42a7-43a0-a163-6efeb50b5e46",
18 | "ef0f8cdf-1e74-412c-9a76-815ceadab388",
19 | "36d2d68c-71da-44ba-8421-b7e57e34bb07"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-e3d28d4f-e72e-430a-92f8-263cd01ea452.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e0f49706-84c9-412d-ae8a-8f0542fa890d",
5 | "a47a1a8a-f853-4021-9578-638e8178fbb6",
6 | "9fed1e10-4d32-4482-bb8a-7332b87b794f",
7 | "d778dda5-6659-4462-9c19-d4d625d04974",
8 | "692d1af4-6523-4c4c-82e7-fed8faa29688",
9 | "dd73c4c3-3b09-4701-801b-cc92eaf33e93",
10 | "e8d5499c-2b14-4501-90db-973142cc3b28",
11 | "9cbf50b4-1237-47a4-affa-332d83ada494",
12 | "453c7312-d23c-4a09-8f7f-019931287567",
13 | "8131d1f8-61c8-4dc5-be92-d0725f165ea1",
14 | "abb057ce-1ce2-4534-b5dc-dd7abc27fa72",
15 | "edeba402-7b8e-4fe0-bdd6-1eb91fc7d0ba",
16 | "42627190-f17b-44cc-bf37-6480e44681ce",
17 | "0bd29c4d-0c75-4a36-8803-646148c9fc18",
18 | "c1ffed95-feac-4bb3-b45f-6dfdd1ca73f1",
19 | "5613c066-00fd-46cf-ad75-9c9de4fb773c"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-e8feed24-4bf6-41b5-84a7-6134f9160cc1.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "518bf55c-e3f4-4078-96a0-92301c95ab02",
5 | "0a858acd-bea3-4a53-ba1e-1ac8faaeb778",
6 | "040d887f-7e12-4041-85d0-2f01a651e4a6",
7 | "004bbdd1-543c-4a36-81c2-4bffa97269e8",
8 | "2c122b60-b41d-4161-858c-4dae8e9f0b0b",
9 | "23422a36-f518-4e0b-aec8-e901a9617e10",
10 | "e61534e0-9e31-4153-b0d8-5608465751c3",
11 | "3bd9c235-f796-4c98-af0c-4534ce84d1b7",
12 | "d08454cd-0889-457a-ab90-2f4c52d1fc4a",
13 | "f99806d7-bcd6-4c68-9084-bb60e317e3a7",
14 | "6c933f98-253d-4db5-8dc6-7600ee8c4346",
15 | "1c8ba9c0-052f-4d23-9f97-5ba6b88dee30",
16 | "9b522935-900a-4649-aacc-f5f50d455118",
17 | "fb34269c-7388-439e-8c63-29735124e448",
18 | "0648882a-df3f-4b7f-924e-ae14ad7d4ccb",
19 | "79888a00-2038-40c3-9bce-c952a63e0a04"
20 | ],
21 | "aggregationResults": [],
22 | "total": 16
23 | }
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/blocks/equation.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "2ce1e02a-e603-457a-9ccc-f291562d890a",
3 | "version": 11,
4 | "type": "equation",
5 | "properties": {
6 | "title": [
7 | [
8 | "\\displaystyle {1 + \\frac{q^2}{(1-q)}+\\frac{q^6}{(1-q)(1-q^2)}+\\cdots }= \\prod_{j=0}^{\\infty}\\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}, \\quad\\quad \\text{for }\\lvert q\\rvert<1.1+(1−q)q2+(1−q)(1−q2)q6+⋯=j=0∏∞(1−q5j+2)(1−q5j+3)1,for ∣q∣<1."
9 | ]
10 | ]
11 | },
12 | "created_time": 1597774249622,
13 | "last_edited_time": 1597774260000,
14 | "parent_id": "7820b2d5-3007-47b3-8e31-344eb06fbd57",
15 | "parent_table": "block",
16 | "alive": true,
17 | "created_by_table": "notion_user",
18 | "created_by_id": "db401f86-4012-4d18-9445-236978ef32df",
19 | "last_edited_by_table": "notion_user",
20 | "last_edited_by_id": "db401f86-4012-4d18-9445-236978ef32df",
21 | "shard_id": 924403,
22 | "space_id": "fde5ac74-eea3-4527-8f00-4482710e1af3"
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/sync-pointer-block.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import {
3 | type Block as BlockType,
4 | type SyncPointerBlock as SyncPointerBlockType
5 | } from 'notion-types'
6 |
7 | import { NotionBlockRenderer } from '../renderer'
8 |
9 | export function SyncPointerBlock({
10 | block,
11 | level
12 | }: {
13 | block: BlockType
14 | level: number
15 | }) {
16 | if (!block) {
17 | if (process.env.NODE_ENV !== 'production') {
18 | console.warn('missing sync pointer block')
19 | }
20 |
21 | return null
22 | }
23 |
24 | const syncPointerBlock = block as SyncPointerBlockType
25 | const referencePointerId =
26 | syncPointerBlock?.format?.transclusion_reference_pointer?.id
27 |
28 | if (!referencePointerId) {
29 | return null
30 | }
31 |
32 | return (
33 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/search-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { cs } from '../utils'
4 |
5 | export function SearchIcon(props: any) {
6 | const { className, ...rest } = props
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "stream",
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": ["build/**"],
8 | "outputLogs": "new-only"
9 | },
10 | "clean": {
11 | "cache": false,
12 | "dependsOn": ["^clean"]
13 | },
14 | "test": {
15 | "dependsOn": ["test:format", "test:lint", "test:typecheck", "test:unit"]
16 | },
17 | "test:lint": {
18 | "dependsOn": ["^test:lint"],
19 | "outputLogs": "errors-only"
20 | },
21 | "test:typecheck": {
22 | "dependsOn": ["^test:typecheck"],
23 | "outputLogs": "errors-only"
24 | },
25 | "test:unit": {
26 | "dependsOn": ["^test:unit"],
27 | "outputLogs": "errors-only"
28 | },
29 | "test:format": {
30 | "dependsOn": ["//#test:format", "^test:format"]
31 | },
32 | "//#test:format": {},
33 | "dev": {
34 | "cache": false,
35 | "persistent": true
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-icon.tsx:
--------------------------------------------------------------------------------
1 | import { type CollectionViewType } from 'notion-types'
2 |
3 | import CollectionViewBoardIcon from './collection-view-board'
4 | import CollectionViewCalendarIcon from './collection-view-calendar'
5 | import CollectionViewGalleryIcon from './collection-view-gallery'
6 | import CollectionViewListIcon from './collection-view-list'
7 | import CollectionViewTableIcon from './collection-view-table'
8 |
9 | interface CollectionViewIconProps {
10 | className?: string
11 | type: CollectionViewType
12 | }
13 |
14 | const iconMap = {
15 | table: CollectionViewTableIcon,
16 | board: CollectionViewBoardIcon,
17 | gallery: CollectionViewGalleryIcon,
18 | list: CollectionViewListIcon,
19 | calendar: CollectionViewCalendarIcon
20 | }
21 |
22 | export function CollectionViewIcon({ type, ...rest }: CollectionViewIconProps) {
23 | const icon = iconMap[type as keyof typeof iconMap]
24 | if (!icon) {
25 | return null
26 | }
27 |
28 | return icon(rest)
29 | }
30 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-checkbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-367ecf41-f124-4652-bb7d-464b3789aefd.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e3bf5e62-9eae-449f-a777-175e1def8b80",
5 | "5bdac465-4c43-4d58-88be-5f83a2ab2f43",
6 | "aa76e519-6aed-470c-87d5-00d28fc8e5d1",
7 | "32002064-7d3d-4cbd-9a52-a9bcf3c61448",
8 | "ee43e779-ef6a-44e0-b390-b27a73e3a8f0",
9 | "a69f0451-9638-486c-ae55-4a0c382e13f3",
10 | "cd72e8a3-07a7-4f90-924b-704d275dd197",
11 | "0a65cca9-fcff-423f-8bef-a4ce66aaece8",
12 | "df813a44-8057-4304-bb08-71a3547bbf3c",
13 | "5d6c4bdb-7e33-4f83-a09a-94729a73628f",
14 | "0f57d9f2-b687-4a7a-839f-8ea642c1b34e",
15 | "a246b542-87de-4c11-af6b-ef6c87c04b25",
16 | "746dc4dd-7b22-4777-b34c-0430315bfed9",
17 | "9bf37264-5772-454c-9406-b7d220a8c5cd",
18 | "3ab63431-c603-4693-ab9e-e6d58148df7d",
19 | "1f0a5f7e-8858-40e9-a779-303422f6d7be",
20 | "f877266a-4d95-45dd-ade4-8ac248a52dbc"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-3815e8d7-e5f2-4399-a761-9ceaed565257.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "a28988ab-b85c-4c07-9465-49a32a3bb34c",
5 | "3a6b159d-905d-4d63-a623-e85ad03ce964",
6 | "18cdf4a8-a6b4-44b4-89dc-e787ec8e1bd6",
7 | "2f4720a8-875b-4980-9d14-54ec2d54b435",
8 | "2ad5c149-4539-4f0e-8a36-4f6bae9540a2",
9 | "f8fcb6f0-1d50-4de7-a0f2-040c1d8d295b",
10 | "3ee3ee0f-f0d2-4a1f-acb6-151b633296e3",
11 | "499b8f7f-007a-473e-900d-a641445ba1b7",
12 | "477de7da-a3e1-4c46-9e3a-5952af73e44b",
13 | "897ef5d0-a209-49ee-909d-489af1423e15",
14 | "17c847ff-bdbb-4c2a-a00f-6043f22204a1",
15 | "dedf24ff-d412-44f6-a7b7-009b7691f8f8",
16 | "654f1d0e-1c4a-4b68-abf0-49f82690555a",
17 | "68e07892-66d9-4e15-96b2-c169c270e81a",
18 | "11442b43-4920-4104-a5a8-0dd806c70900",
19 | "348cef4e-a2f3-4339-8fde-dd9b5434955b",
20 | "e8325846-2c81-44bc-bffe-7be3e11b2fab"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-385ed123-6a39-46e5-984c-9704bcfd6494.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e6ebae0a-867e-4aa9-8449-45b425503ad7",
5 | "62f5cbb8-df1e-4288-b8d7-b0a4eb141911",
6 | "858e3b08-f5fa-44d3-9579-d31a1b37997e",
7 | "6ad51635-0f9b-4f01-a6b2-7ce5c2e5dd2f",
8 | "d6641250-13c6-4c2c-95e5-e04bfe2d7338",
9 | "129aaae2-0713-4c89-a3f7-d4b8bd3b6e6c",
10 | "668439ae-81b2-4f07-802c-4c2dc2109a31",
11 | "6c04e02d-76c5-4a7e-a421-b4ad0efc3e3a",
12 | "2e882951-6345-4584-92cc-bb496a0832a9",
13 | "d5747412-75f3-43c2-9dd2-e82956c0f11c",
14 | "aca8c73a-ed85-4cbd-8af9-9ea099791fbb",
15 | "0b0d01f1-04bb-4024-aaf1-fd1a81b8315a",
16 | "36d03792-4f52-4e05-af3d-2f1833d8a0f2",
17 | "7b029780-64dc-4019-a040-ab21b1ac175e",
18 | "6933a5d4-24ef-4705-a0e7-aaf627668d4e",
19 | "781dbbf8-028b-401f-912a-ec0984a7e884",
20 | "b087f212-0ed6-4b35-9650-bdc18e68b030"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-448a76b8-847d-4a40-a494-9a6da9c41a58.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "a9747787-f2d7-48e0-ac80-2ca1c2bec19e",
5 | "f40275d2-3c48-49ab-82f7-8f4032e95452",
6 | "217469c1-0a42-4f90-a2c7-3aed5ac01ac2",
7 | "622e9abf-ca89-4a64-a43f-7633f2bcc157",
8 | "56e9c69a-5b13-4de4-ac7d-97e28f43a907",
9 | "2e35e5fd-1f19-4832-a717-e9d0217e0902",
10 | "8af58cfe-b746-4dc0-9dec-35824c67dcda",
11 | "d82575ca-95bd-4bd1-b430-e3af06d1cc57",
12 | "5426599c-94a1-4306-a35f-a07a5c0d19eb",
13 | "3d2134ed-9279-4195-876d-cb645a6870a8",
14 | "a8a84326-0219-43b1-ba4e-53c655c76ef9",
15 | "462b6820-aaf3-4cac-8a24-c961e2eb57b0",
16 | "2650a90e-d204-4817-b86a-f96156bc42d7",
17 | "f5dde43e-3ff4-4643-9ceb-0277ae7e6428",
18 | "91a215b7-d355-4eb1-b57c-3587c98e2625",
19 | "1fc82e99-08b1-4275-8c3b-cf75f9d9a3c1",
20 | "c48d4fad-35e3-4c51-be9b-620c0203d6c0"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-52718a79-d339-4d26-873f-734004af8823.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "dcc5756d-5f36-4119-bb6f-f70674e57ed4",
5 | "785631ae-a5a8-4d2c-9ce6-0fafb6c0930a",
6 | "33074977-d5e8-40aa-8cdb-8e7f82ba3bf2",
7 | "bc125aeb-c287-4282-a196-d34cb432298e",
8 | "57e1309d-433d-4356-a397-c6ffcb3038b5",
9 | "35f647fc-94d3-41e9-9f6a-8bd6a11cad7e",
10 | "54aa4736-d952-4873-a1c0-eb3a9985a9b3",
11 | "ca9d5f4e-54b2-4a92-80bc-c943eff69f20",
12 | "e0019987-0da1-43d0-8dd9-eef326282b49",
13 | "98244082-609b-4e4c-82f4-3c5a62446688",
14 | "699cc67e-a9fc-4cbb-a264-bac18dc422f2",
15 | "5957ea43-6466-4016-b68c-6c2ee342c0dd",
16 | "88fe3c43-ec2b-4804-b8ee-a6e085536b85",
17 | "78823d5c-e17f-4f51-a648-a77626259cdd",
18 | "e4a178c4-0d0d-4c5d-9f90-ed16b53c60fa",
19 | "bd458279-307c-4a15-8838-42e6cfa7ffee",
20 | "2758897b-91e1-41f1-8360-ad7872e8c15b"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-5662c1f4-a011-4629-9616-3a2d53f6c9af.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "06e60f3a-dd9c-4934-986b-68c18987dac1",
5 | "378291b3-7b98-4444-98df-07af3a99989a",
6 | "461091f8-f1f0-4e99-b969-a7e1e9b6ae3c",
7 | "8854951b-0821-4239-a8a4-82ec6c764c9e",
8 | "f506dae2-41fe-4c07-81ce-452ef509aee5",
9 | "b49c42f5-fe7c-4c23-aa9a-ebdd7cb4822d",
10 | "331608be-0978-4f03-88f6-c74118a5538c",
11 | "b8dcb266-39f8-418e-95e4-37dc9b183b3b",
12 | "5903bd9d-026a-41b2-ae39-58c7348a9167",
13 | "5267f716-9b46-4784-81cc-b6227f932ccb",
14 | "9eabc0da-62ca-4263-9b37-237fc821d42e",
15 | "873b6533-79bf-4331-ace1-1c6d51fc9872",
16 | "676a23d9-5b72-4846-8a39-d5ec6893fda0",
17 | "1c4af320-8de1-4fb8-a0ed-3e3998023c85",
18 | "c502ac7c-4f87-4dc1-b0a5-8eacba5ce865",
19 | "32c355a9-ad70-4738-b27e-19357a1c59f7",
20 | "8a87d4a3-ddb0-47f1-9346-488e0dd7ee70"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-5742eaed-3c36-495a-bde5-7d33cba0ddb5.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "4fe48fff-7247-4872-9df0-0ae1bd512e7b",
5 | "fbfc7c0a-46e6-410a-b7c6-8d4efcd2b97e",
6 | "b8b5853c-f8ef-41b2-92cb-fca80e9531c4",
7 | "c7047621-214e-4bc0-9a23-12fcf26a11e0",
8 | "aff15622-7df6-4978-b040-d9cb89dc270d",
9 | "9daa69f7-d373-4155-bcdd-21039929c55b",
10 | "8a3b0b94-7551-4836-af16-00e8babf7ad5",
11 | "c0cf46e3-3c12-49f1-a8fd-eeb01d715a38",
12 | "860c5666-66ec-4c82-94e2-650df0525be8",
13 | "8b9e79af-0a4b-4372-90a1-be49cb02dbd7",
14 | "d905c027-6f15-4b54-a3ac-f5b89e9baf6b",
15 | "ba1e1e72-dd81-4ca3-a042-832d08c0db64",
16 | "d068369f-757a-4365-b78e-6bea4a7f929f",
17 | "78fc64d5-8a93-4458-b31d-8783a08480a6",
18 | "ddc12215-3538-4fab-8847-eb7b9ee906e2",
19 | "052887bd-7623-4b48-b5ec-84c646a1cf1b",
20 | "bebb6cc0-96b0-4123-a8fc-b98faa4ad5ca"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-5c20b593-23d4-4884-9102-3ef3b1049273.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "d75a9827-3ec6-4943-b361-10b8c4821ff1",
5 | "159ba45a-670d-4a0d-bfd2-d54146e72740",
6 | "28c7d311-d951-42cc-9723-439f68448bff",
7 | "6d1d75e9-3195-45d2-9016-6a14f3882ef4",
8 | "c936e013-e4b8-491b-b3c0-4111619bb0c4",
9 | "f16c45fe-f2c8-45a7-b181-d64605bd5840",
10 | "95a2889c-1a7d-406f-917c-8d9794314c0a",
11 | "d6c06029-acde-44e1-836f-e1486ebede6e",
12 | "bf1b7e65-98ef-4af1-aaf3-9e345ffab9f7",
13 | "d3981dcf-4829-49f5-9a51-b88a202b91a4",
14 | "6930de26-1600-4bc9-ba77-6ee56e13a6d1",
15 | "af6fed24-d7a7-4279-a371-141b916f29c9",
16 | "e6349b92-5944-4be7-8a47-5851f4d19d09",
17 | "36249588-5867-4eab-a5d8-44479a1d7531",
18 | "97b68898-a0a4-447c-b144-5ccb91a8684e",
19 | "bacd4cb0-8b58-480f-88d1-fd4f92e43660",
20 | "390c32a6-f4b5-4120-84a3-d1a85c88c18c"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-63717bf3-3af6-4112-989d-acbe665ccf91.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "0b0bc477-fe3c-46d9-8b4d-0558d052ad35",
5 | "65d84d86-39bb-4171-af71-05f99707bf85",
6 | "e1aaa0d5-73e4-419d-b5d6-e414ba0d4e55",
7 | "5994f819-887e-4e3e-b7dc-0095f0b4df7d",
8 | "07238e54-8e66-44a7-8a1e-116238320caa",
9 | "e9fa60a7-3b95-44bd-bcb3-398227dc0d93",
10 | "ae1d419b-7091-4caa-a158-dfe922433575",
11 | "053abff1-2986-4ff9-ba0d-dc3d9eca8a73",
12 | "19b92c37-9784-4471-b973-0e3953433fe5",
13 | "cb9d9d33-235b-48ed-aee1-94f4d2874e30",
14 | "8fe0836e-7ac1-41c3-904c-12b8d6b72f1a",
15 | "de516906-d101-485a-bdcc-aa11b932ec87",
16 | "e1986719-1c20-431a-9c69-292a98a2f986",
17 | "7a9eb58b-4c83-4418-a5c5-702993da5eca",
18 | "7f32bc1b-7598-47d2-88ed-692d1cdf2f1a",
19 | "49b2b551-8b85-4bdb-bd0b-484a068b29c3",
20 | "d1e18c54-56d4-448d-8693-0ab2b32b69b3"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-7687ad1f-ccbe-4abb-8e95-a3685543aefe.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e5fa63eb-9a90-4393-9559-6d468a45713b",
5 | "9afef1ef-b1eb-4afa-bc0a-a529bd0980e5",
6 | "d26aeb07-2605-4293-9b12-38ebd8a4453e",
7 | "d2637657-3107-4ac0-ac4d-b86b1d112ae4",
8 | "1ac05e78-817b-4def-be68-e4abf9931099",
9 | "a0a1cd9a-71bf-409d-8633-691ed657b5af",
10 | "2cc221c5-bad1-4d70-8fca-6dbaa28ef17d",
11 | "f4edd0e8-9106-46e1-a983-a390ce26a67c",
12 | "2ccbad8c-b6ce-4092-b56e-67556e19834e",
13 | "95b21335-9403-4491-b07e-dc7aa7b4831b",
14 | "bd3fad3d-eb60-4523-a725-3ec994b96165",
15 | "60e2f725-8a98-4c9f-826f-5c989dee34e7",
16 | "aadd128a-2301-4371-82fc-745b7f12d65b",
17 | "ffc132d9-6f51-4100-8de8-ffd44f07bb1f",
18 | "e250707e-beee-4f50-a9f4-01ea03186929",
19 | "0240087b-223d-4c18-aa50-87e72836565b",
20 | "1fb18673-c269-41dc-9588-2adc35ebdc53"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-85e4d626-09cc-45f6-9a28-07d5d490ca1d.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e47dfd5d-eff8-443f-997e-d4f87df4eba3",
5 | "0cf08062-1177-4438-95ca-f08ffa08f68b",
6 | "712feb1b-3340-4276-9d72-1cbb957b12d2",
7 | "8fcd3b85-515b-4f64-b56c-0bfa25378c1f",
8 | "923fddb3-f155-4ed0-8f73-4b35f548313a",
9 | "bf7af6a2-79f0-44f0-9355-0099157e7883",
10 | "4c6173b9-d127-48e5-b06c-5b1199e1b668",
11 | "ef13dd90-81d0-4e13-917e-28cf366213b1",
12 | "e8afe3d1-89bc-4304-99c3-1432ac893c35",
13 | "50395c04-2584-4a35-b3eb-f1057fdb5d3a",
14 | "b27d3c7b-dfaa-4ca3-8a58-76a71d4e1249",
15 | "5f7db689-4d74-4a04-9feb-0fb1318a5793",
16 | "6d2cd5e5-cdd1-46d8-ad7f-ecca8a67c563",
17 | "eef49b94-5e83-4f4d-89cc-be1f3fc51194",
18 | "f1740d6e-aecc-46b8-9593-c03ac98428c2",
19 | "50988a22-3fc1-4cfa-9bcf-f53225e12ee5",
20 | "fb7e8bc2-1c6c-4bb9-8417-f3f35ec81c40"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-93fe8a38-e32b-4c6a-87f2-03f1a438de75.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "116d9fd5-23d5-4b40-a5f5-4d17c3ec4878",
5 | "d7574446-5fef-4bd3-aabb-1efb11d790b1",
6 | "1145281e-c801-4d5a-85e6-a65edc2f76f0",
7 | "b7cdb0ca-34e3-456a-bb24-4f63f04627fe",
8 | "5dcbf508-1f19-4ae5-9993-3f0e8876681b",
9 | "31d71a35-0697-4d79-9788-0a0aece5a9f1",
10 | "aa37a93c-ecad-45a1-90d5-3df954c7fa40",
11 | "d6a07a3a-00c3-4513-94ee-ac54b428377b",
12 | "e55031d0-8855-4f6c-a937-e9e55cb665b9",
13 | "a5611170-3b44-4d9b-aae5-9d7002b673cf",
14 | "73d4b541-7a47-4205-a96d-6776f8eefec6",
15 | "947682f7-7e61-4583-a0f4-70280db629f0",
16 | "d7a971e2-2ae6-43ce-ba54-8356afc680b9",
17 | "6cb98fa9-927a-42db-a7df-665020bcb456",
18 | "4aee59ce-3c76-4cb5-b720-545266a1a1f2",
19 | "e708b1da-967c-4d68-a6d6-a5987b5c83b3",
20 | "0763d43d-70c9-4ad4-ada5-cb0374b8d730"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-99cd3dfb-97fb-493d-bfaa-518bd872dda4.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "d389225d-1ed7-4234-a83d-85eb3ed46c33",
5 | "3cf9537e-e47c-456a-9cfb-1f6fbecc49b6",
6 | "672ab20d-33c3-4863-b94a-a20b4191f9d2",
7 | "af584923-1332-4b4a-b6a0-d1edd5dea6a9",
8 | "163d744c-1fd0-4207-ae19-b1d8d6d4aecb",
9 | "6db88bd8-fcfa-4409-8260-9192d26e4596",
10 | "1cb564d0-2918-4625-8c82-1812df38e104",
11 | "f5a4d04f-31e7-4d10-99f3-bd90b6fac5e8",
12 | "be8a710a-78fc-400f-8852-72f976b04b4a",
13 | "8812937a-7b9f-4e59-92dd-72d80b0f0150",
14 | "fef63b45-91ec-47d5-bd32-27d27dab419f",
15 | "f2c125eb-97d0-40fa-b147-d325b8689f99",
16 | "1b47d758-678d-44bf-8fe4-e432618b6282",
17 | "31dd97fd-a0a6-41e1-83b9-f2fb91c58550",
18 | "91bd4399-4e91-418c-9906-b360a4a32107",
19 | "db375bde-34fb-44ea-a3f2-75e41fa92e93",
20 | "033f25e6-a8f6-4ba7-8b56-508e95be5f15"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-9fdf39a1-64c3-464c-a4c9-f3e4d9a9aa2c.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "bade9948-e8d0-4706-b135-f4ac402f363c",
5 | "8bc1f4f2-8efb-4056-9253-a2cd313a0664",
6 | "5e418731-93cc-4256-a193-4e4b6e0a8a50",
7 | "618353f9-1dcb-4894-bda3-a95780ef9897",
8 | "b1ed6778-ccb0-461e-917c-ddd64e0af148",
9 | "eb0bea4e-81dd-4de2-8266-6956bccdc212",
10 | "1999a4fd-f30c-44b6-8bad-002af81dda68",
11 | "dc0be7cc-c8a7-4197-9498-b80b0af2293f",
12 | "4fe86627-1790-4a53-9351-810fec002c5e",
13 | "4f3a36b3-1817-4451-a692-73e6d2d9566f",
14 | "726b87b3-bc9d-4af4-a804-03f1be3340fd",
15 | "56aaad71-3013-4c49-b37c-5c227c79f299",
16 | "8fade8ad-a605-41ba-9652-57e4b064d0ff",
17 | "f41fccaf-fdaa-4baa-8696-31b6ecad12f7",
18 | "081f52dc-abad-4119-b79e-94b155327d62",
19 | "b40cda73-b89a-44fc-8840-78aff8792427",
20 | "f10dbc1a-5366-4da7-b41c-5c1bae52d817"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-ab6c9a56-5322-4bd1-a384-7797a81478c5.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "44bad171-b4ac-44b9-ad58-b83c95efc455",
5 | "680ce12f-b1a1-4696-ae20-c9d20a36fb8a",
6 | "dcda924e-8b52-4461-ab5a-6593ced99dba",
7 | "d814bdb7-8fe8-497d-bdfd-11c32736b4bd",
8 | "1ef2791e-d8a4-4234-8327-ba0bc066bf66",
9 | "9fb342a5-281b-43ca-9ef6-0b5c5c1ee2ed",
10 | "a1bed86a-7bef-46d5-9932-4e94c8c092ba",
11 | "1893193b-bdb6-4531-9fee-d9f4405e94df",
12 | "bfcfc733-44f6-43fe-bcae-43a436947aec",
13 | "1df0cb61-e72f-45de-9778-f17a6a7faa02",
14 | "db5379f5-87e9-48b7-969d-4b82c1ce174e",
15 | "c2825fad-7403-4f4a-b251-302fca95954d",
16 | "4f674cd3-80df-4dc7-b882-75f5f181cabb",
17 | "eb6703eb-70ff-4d7b-abd0-925e1531a77a",
18 | "48bda606-dc77-4e13-836e-c3e32ede5922",
19 | "1eda32b5-dbb0-4f9c-b4b7-916beb8644df",
20 | "5d092eea-2814-4a33-bcd7-2077b149b831"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-b6c6b669-659e-4487-8d48-8a538b8a0d82.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "50bcbdc0-0f62-4d11-9b8d-1acfb33064c9",
5 | "ae68ad50-b170-4e2e-8913-a9fba4fb6685",
6 | "578d7618-26a6-41b7-9593-9d43efca2816",
7 | "3399ed56-0507-4e71-a964-6e59e87c24af",
8 | "5bbd362c-20d8-444f-ba64-e7db752bee6e",
9 | "6b38233e-c62f-4ce8-b959-26256e77246a",
10 | "d4ef28bc-ceec-46b1-ac94-d83d05008953",
11 | "ba8bccf0-3f72-4999-81de-654f8f521c83",
12 | "d450c617-527a-4b42-9d5b-425b0fe6cb24",
13 | "e82bcf69-7ab8-4716-962a-a990a1320c68",
14 | "48ffab96-cf5f-4e84-8491-a6939e631a24",
15 | "57a4a7a7-6c0b-4024-b4e0-77698d8e9d42",
16 | "88e820c8-cf3a-483c-b70a-f95a1de175ba",
17 | "58a92c7b-b52a-4547-8f3d-a4250ff3416a",
18 | "caf05638-c338-45e2-8708-69d11382e16e",
19 | "c4a83c17-fc38-4e08-8edc-97d2fb8112c4",
20 | "001a3b09-88e9-4a6a-8c90-e524a04c2f27"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-bdc489ac-13dd-46bd-97f9-86866d76bc43.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "15e5035e-396a-4822-a510-e1a6f42f959a",
5 | "458a640c-0440-4bab-800d-6addb1fce4a2",
6 | "81c1ea34-eeca-446f-b042-3a4ed41702f6",
7 | "83d039b4-d75b-433b-bf0a-6abb0d42d7ab",
8 | "6c51dece-aadc-4e2a-8426-17f693128f0a",
9 | "e26d6bd2-6f2a-480d-913d-2ecd6efc4c03",
10 | "99936274-2eae-4def-92f6-34b46649dbd3",
11 | "39615d67-a5ba-4766-b1de-d5f2ccb262be",
12 | "9a3b3071-f994-4fd2-8e31-495d87617b8d",
13 | "46912a1e-c529-40e6-a4eb-e9e33af9eab8",
14 | "eb058479-abe5-4bef-ab45-fd8a02417c57",
15 | "6fe85469-68e9-4444-8159-f1e7bc54b77b",
16 | "9b8b837a-085e-4840-a3b0-a669167f7268",
17 | "25ebb018-1bf8-438a-9ba0-c16c033d527f",
18 | "f3f7f355-52a9-4931-b7d2-19b637b6ed02",
19 | "ed8a572f-f2d0-460a-89d3-d475f33c5974",
20 | "427341bc-79a3-4ec5-8987-a91dbd65e041"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-c14e33fe-19d2-4266-a935-0ef6528a77ff.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "4b63e868-7277-40ed-b830-7d72d5bccf41",
5 | "065d7fa4-c7d0-45b7-a95f-5535e48c7ff3",
6 | "f3c1f8ce-34d0-4816-ac70-bf77c0739c25",
7 | "ec525e26-c898-49a5-b930-de3ab98eedb4",
8 | "59da3e17-f388-428d-a7a5-ed4ffdf2863f",
9 | "235ee243-e12e-4d0c-b044-5ae62d261fe4",
10 | "7ba6a6ed-f214-40c1-8031-ed5a7d4dc5ed",
11 | "00d0e651-e8c0-4b15-a11d-2b68bceb9119",
12 | "7578f7b0-914a-4f66-9251-367af2ba0325",
13 | "9dedabed-701c-4b05-b317-4b6b75a2481f",
14 | "465066e3-be56-4ce0-9482-92fc538e7693",
15 | "21ee8a3d-77a0-4681-8d38-d68ab459596a",
16 | "53eff952-0112-4c9c-b230-59cb09bd101f",
17 | "f41f23d4-640f-4200-8567-c30a84548be8",
18 | "caee42ee-0955-4621-85be-f98869854b5d",
19 | "57f0d621-3035-4676-8e64-0bba57105ce3",
20 | "db2d6578-3120-4987-ae63-c865722c5b32"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-c34ab79e-df2d-4a6d-af71-8aef61a65aa7.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "734f8ebc-e117-40d9-b39a-e46578ef805e",
5 | "f14efca6-b40b-4411-8ceb-de5b87cb113c",
6 | "0e968a69-2b1d-491b-903d-1a02946e6570",
7 | "53452992-30fb-4120-b21a-db76e99ab839",
8 | "7cfcaa96-bb56-4646-8bad-cc8fd58aea57",
9 | "d539802b-e51c-4187-8061-b8b670f083a7",
10 | "b0ead175-abb3-4fc1-9338-f7ef9c77036b",
11 | "65af3799-3b7e-4fc2-8dce-ba2c520f6bd6",
12 | "eeeca8b7-af12-487c-aaab-ab3fbbf6ab4c",
13 | "95a42416-cc68-417c-abaf-0987a2eee8d4",
14 | "8fa213b5-2031-48db-8da9-94dc34699fcb",
15 | "e652949a-bef8-43f1-baa9-2d760b23deb1",
16 | "5316faee-fcf7-4a48-a572-40c7e809cce1",
17 | "c2e04e3d-74d3-47a0-b4ea-0c2bf58ebf2e",
18 | "766b743c-1aa6-468f-b94c-a8d78c27d78e",
19 | "0a9655db-94cf-4b07-8414-18b2c12b1f88",
20 | "3aa043eb-2935-4ccb-b248-d13a97f305cd"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-dac0d378-1774-47fc-a3a3-103ae120a3c1.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "67f33590-2cc2-4a1b-a5ad-5e065187934f",
5 | "a2cead6d-b40d-4882-99a5-0770a22c618b",
6 | "48fd037d-56f5-460a-b324-ad7d6cc07e45",
7 | "262921ae-76f4-4f85-b88c-f5d7389788d2",
8 | "a9d3d864-127d-44c1-86b3-4cc8dba3aacd",
9 | "6cd139ad-3bb1-497d-9866-fb8d97486279",
10 | "db024394-18b0-4b48-8ec6-c5dcbc623d36",
11 | "dd379e8b-3896-41d4-95f8-fff5ab73de5c",
12 | "61f6ec91-aac3-4211-bd15-f6e85e3800bd",
13 | "07610753-46a4-4610-bc36-d250db60a35d",
14 | "aa4fa68c-b98f-4838-9e6a-930d58ecac2d",
15 | "c08a13b1-d7c5-4e78-94ef-8b149c4660a2",
16 | "3e605533-879f-4167-972e-1454c44eb0fb",
17 | "96406da9-b28b-4899-8885-9883f6909ed6",
18 | "f6030e27-cb91-41ca-8379-8a1dac44ffb6",
19 | "49f89791-85b4-4cd1-98f0-2219f96dfe7e",
20 | "6c4293fd-e16e-4d5a-9171-b0a6f7e03998"
21 | ],
22 | "aggregationResults": [],
23 | "total": 17
24 | }
--------------------------------------------------------------------------------
/packages/react-notion-x/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # React Notion X
6 |
7 | > Fast and accurate React renderer for Notion.
8 |
9 | [](https://www.npmjs.com/package/react-notion-x) [](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml) [](https://prettier.io)
10 |
11 | ## Docs
12 |
13 | See the [full docs](https://github.com/NotionX/react-notion-x).
14 |
15 | ## License
16 |
17 | MIT © [Travis Fischer](https://transitivebullsh.it)
18 |
19 | Support my OSS work by following me on twitter
20 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-multi-select.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-client",
3 | "version": "7.7.3",
4 | "type": "module",
5 | "description": "Robust TypeScript client for the unofficial Notion API.",
6 | "repository": "NotionX/react-notion-x",
7 | "author": "Travis Fischer ",
8 | "license": "MIT",
9 | "main": "./build/index.js",
10 | "module": "./build/index.js",
11 | "types": "./build/index.d.ts",
12 | "sideEffects": false,
13 | "files": [
14 | "build"
15 | ],
16 | "engines": {
17 | "node": ">=18"
18 | },
19 | "scripts": {
20 | "build": "tsup",
21 | "dev": "tsup --watch",
22 | "watch": "tsup --watch --silent --onSuccess 'echo build successful'",
23 | "clean": "del build",
24 | "test": "run-s test:*",
25 | "test:lint": "eslint .",
26 | "test:typecheck": "tsc --noEmit",
27 | "test:unit": "vitest run"
28 | },
29 | "dependencies": {
30 | "ofetch": "catalog:",
31 | "notion-types": "workspace:*",
32 | "notion-utils": "workspace:*",
33 | "p-map": "catalog:"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/collection-view.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { type CollectionViewProps } from '../types'
4 | import { CollectionViewBoard } from './collection-view-board'
5 | import { CollectionViewGallery } from './collection-view-gallery'
6 | import { CollectionViewList } from './collection-view-list'
7 | import { CollectionViewTable } from './collection-view-table'
8 |
9 | export function CollectionViewImpl(props: CollectionViewProps) {
10 | const { collectionView } = props
11 |
12 | switch (collectionView.type) {
13 | case 'table':
14 | return
15 |
16 | case 'gallery':
17 | return
18 |
19 | case 'list':
20 | return
21 |
22 | case 'board':
23 | return
24 |
25 | default:
26 | console.warn('unsupported collection view', collectionView)
27 | return null
28 | }
29 | }
30 |
31 | export const CollectionView = React.memo(CollectionViewImpl)
32 |
--------------------------------------------------------------------------------
/examples/full/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-x-example-full",
3 | "version": "7.7.3",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "deploy": "vercel deploy",
11 | "test": "run-s test:*",
12 | "test:lint": "eslint ."
13 | },
14 | "dependencies": {
15 | "@notionhq/client": "catalog:",
16 | "classnames": "catalog:",
17 | "katex": "catalog:",
18 | "ky": "catalog:",
19 | "lqip-modern": "catalog:",
20 | "next": "catalog:",
21 | "notion-client": "workspace:*",
22 | "notion-compat": "workspace:*",
23 | "notion-types": "workspace:*",
24 | "notion-utils": "workspace:*",
25 | "p-map": "catalog:",
26 | "p-memoize": "catalog:",
27 | "prismjs": "catalog:",
28 | "react": "catalog:",
29 | "react-dom": "catalog:",
30 | "react-notion-x": "workspace:*",
31 | "react-tweet-embed": "catalog:"
32 | },
33 | "devDependencies": {
34 | "@types/prismjs": "catalog:",
35 | "cross-env": "catalog:"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-text.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/e777a528-9404-4e96-9f26-0014be705592.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "e777a528-9404-4e96-9f26-0014be705592",
3 | "version": 34,
4 | "name": [
5 | [
6 | "NBA Teams"
7 | ]
8 | ],
9 | "schema": {
10 | "$N1}": {
11 | "name": "Conference",
12 | "type": "select",
13 | "options": [
14 | {
15 | "id": "`8t(",
16 | "color": "blue",
17 | "value": "Eastern Conference"
18 | },
19 | {
20 | "id": "A'Q~",
21 | "color": "red",
22 | "value": "Western Conference"
23 | }
24 | ]
25 | },
26 | "3-5L": {
27 | "name": "Players",
28 | "type": "relation",
29 | "property": "p4nU",
30 | "collection_id": "1a425309-21c0-4ca1-8548-c5915137b566"
31 | },
32 | "f+&p": {
33 | "name": "Logo",
34 | "type": "file"
35 | },
36 | "title": {
37 | "name": "Team",
38 | "type": "title"
39 | }
40 | },
41 | "parent_id": "8a586d25-3f98-4b85-b482-54da84465d23",
42 | "parent_table": "block",
43 | "alive": true
44 | }
--------------------------------------------------------------------------------
/packages/notion-utils/src/merge-record-maps.ts:
--------------------------------------------------------------------------------
1 | import { type ExtendedRecordMap } from 'notion-types'
2 |
3 | export function mergeRecordMaps(
4 | recordMapA: ExtendedRecordMap,
5 | recordMapB: ExtendedRecordMap
6 | ): ExtendedRecordMap {
7 | const mergedRecordMap: ExtendedRecordMap = {
8 | block: {
9 | ...recordMapA.block,
10 | ...recordMapB.block
11 | },
12 | collection: {
13 | ...recordMapA.collection,
14 | ...recordMapB.collection
15 | },
16 | collection_view: {
17 | ...recordMapA.collection_view,
18 | ...recordMapB.collection_view
19 | },
20 | notion_user: {
21 | ...recordMapA.notion_user,
22 | ...recordMapB.notion_user
23 | },
24 | collection_query: {
25 | ...recordMapA.collection_query,
26 | ...recordMapB.collection_query
27 | },
28 | signed_urls: {
29 | ...recordMapA.signed_urls,
30 | ...recordMapB.signed_urls
31 | },
32 | preview_images: {
33 | ...recordMapA.preview_images,
34 | ...recordMapB.preview_images
35 | }
36 | }
37 |
38 | return mergedRecordMap
39 | }
40 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Next.js: debug server-side",
6 | "type": "node-terminal",
7 | "request": "launch",
8 | "command": "./node_modules/.bin/next dev",
9 | "cwd": "${workspaceFolder}/examples/minimal"
10 | },
11 | {
12 | "name": "Next.js: debug client-side",
13 | "type": "chrome",
14 | "request": "launch",
15 | "url": "http://localhost:3000"
16 | },
17 | {
18 | "name": "Next.js: debug full stack",
19 | "type": "node",
20 | "request": "launch",
21 | "program": "${workspaceFolder}/examples/minimal/node_modules/.bin/next",
22 | "cwd": "${workspaceFolder}/examples/minimal",
23 | "runtimeArgs": ["--inspect"],
24 | "skipFiles": ["/**"],
25 | "serverReadyAction": {
26 | "action": "debugWithEdge",
27 | "killOnServerStop": true,
28 | "pattern": "- Local:.+(https?://.+)",
29 | "uriFormat": "%s",
30 | "webRoot": "${workspaceFolder}/examples/minimal"
31 | }
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/examples/cra/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-x-example-cra",
3 | "version": "7.7.3",
4 | "private": true,
5 | "scripts": {
6 | "dev": "DISABLE_ESLINT_PLUGIN=true GENERATE_SOURCEMAP=false react-scripts start",
7 | "start": "DISABLE_ESLINT_PLUGIN=true GENERATE_SOURCEMAP=false react-scripts start",
8 | "build": "DISABLE_ESLINT_PLUGIN=true GENERATE_SOURCEMAP=false react-scripts build",
9 | "eject": "DISABLE_ESLINT_PLUGIN=true GENERATE_SOURCEMAP=false react-scripts eject"
10 | },
11 | "dependencies": {
12 | "@types/node": "catalog:",
13 | "@types/react": "catalog:",
14 | "@types/react-dom": "catalog:",
15 | "notion-types": "workspace:*",
16 | "react": "catalog:",
17 | "react-dom": "catalog:",
18 | "react-notion-x": "workspace:*",
19 | "react-scripts": "catalog:"
20 | },
21 | "browserslist": {
22 | "production": [
23 | ">0.2%",
24 | "not dead",
25 | "not op_mini all"
26 | ],
27 | "development": [
28 | "last 1 chrome version",
29 | "last 1 firefox version",
30 | "last 1 safari version"
31 | ]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-title.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeTitle(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeTitle
12 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/List.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "c0d2d91c-e2a5-4ea5-a9b7-2b2d8ae2d591",
3 | "version": 8,
4 | "type": "list",
5 | "name": "List",
6 | "format": {
7 | "list_properties": [
8 | {
9 | "visible": false,
10 | "property": "$N1}"
11 | },
12 | {
13 | "visible": false,
14 | "property": "f+&p"
15 | },
16 | {
17 | "visible": false,
18 | "property": "3-5L"
19 | }
20 | ]
21 | },
22 | "parent_id": "b2b89bc2-7db9-487a-b834-9047b0e0f5c5",
23 | "parent_table": "block",
24 | "alive": true,
25 | "query2": {
26 | "sort": [
27 | {
28 | "property": "title",
29 | "direction": "ascending"
30 | }
31 | ],
32 | "filter": {
33 | "filters": [
34 | {
35 | "filter": {
36 | "value": {
37 | "type": "exact",
38 | "value": "Eastern Conference"
39 | },
40 | "operator": "enum_is"
41 | },
42 | "property": "$N1}"
43 | }
44 | ],
45 | "operator": "and"
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-bd226193-3647-4ed1-bbe3-a5baa69954c7.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e93ad1c5-28f0-414d-bf38-2ee4942a179b",
5 | "cab48d12-642c-4cfe-ab61-140357800313",
6 | "ae647d94-0c24-43fe-9863-2735765193d0",
7 | "db67ff57-37ad-49aa-9acb-928996d16aca",
8 | "6f337686-c8a2-466f-ace0-ffd050d3ae1f",
9 | "f17e0ebf-c690-4fbe-9aef-32f7c1e0242d",
10 | "36ad2e36-890c-4263-bb06-57882acb8e92",
11 | "be887255-8a1b-4de8-b2dc-fe62d4970276",
12 | "4aa98c40-1dc2-4545-bdb0-116cbd4f8ece",
13 | "dc108e7a-8ec5-4f1f-bca4-87060b5dd29c",
14 | "f5401633-95dc-4884-b5be-f2ff895e2930",
15 | "bc2c88f9-b011-4e06-a493-d8ef870b3b00",
16 | "c6932a55-2df4-4c14-ae50-78a45e6e847c",
17 | "60bfa5fc-f2f6-4af2-bcec-eee315cad899",
18 | "3fc9c51e-1ae8-48de-90b3-2a8ebccdb3d8",
19 | "36030fb8-b864-4f8e-a81e-27086d8c4b42",
20 | "22e7dc5f-a5e3-40cf-87c4-762a57a2ef55",
21 | "4eb264b8-df87-49f0-a0d1-af56908d0bb5",
22 | "f5dc3f45-5d23-4d42-8132-2230fd5d354c"
23 | ],
24 | "aggregationResults": [],
25 | "total": 19
26 | }
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/collection-group.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | import { type CollectionGroupProps } from '../types'
4 | import { Property } from './property'
5 |
6 | export function CollectionGroup({
7 | collectionViewComponent: CollectionViewComponent,
8 | collection,
9 | collectionGroup,
10 | schema,
11 | value,
12 | hidden,
13 | summaryProps,
14 | detailsProps,
15 | ...rest
16 | }: CollectionGroupProps) {
17 | if (hidden) return null
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | {collectionGroup?.total}
27 |
28 |
29 |
30 |
31 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './estimate-page-read-time'
2 | export * from './format-date'
3 | export * from './format-notion-date-time'
4 | export * from './get-all-pages-in-space'
5 | export * from './get-block-collection-id'
6 | export * from './get-block-icon'
7 | export * from './get-block-parent-page'
8 | export * from './get-block-title'
9 | export * from './get-canonical-page-id'
10 | export * from './get-date-value'
11 | export * from './get-page-breadcrumbs'
12 | export * from './get-page-content-block-ids'
13 | export * from './get-page-image-urls'
14 | export * from './get-page-property'
15 | export * from './get-page-table-of-contents'
16 | export * from './get-page-title'
17 | export * from './get-page-tweet-ids'
18 | export * from './get-page-tweet-urls'
19 | export * from './get-text-content'
20 | export * from './id-to-uuid'
21 | export * from './is-url'
22 | export * from './map-image-url'
23 | export * from './map-page-url'
24 | export * from './merge-record-maps'
25 | export * from './normalize-title'
26 | export * from './normalize-url'
27 | export * from './parse-page-id'
28 | export * from './uuid-to-id'
29 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-table.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Travis Fischer
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.
22 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-person.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypePerson(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypePerson
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-phone-number.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/search.json:
--------------------------------------------------------------------------------
1 | {
2 | "s0": {
3 | "type": "BlocksInSpace",
4 | "query": "test",
5 | "spaceId": "fde5ac74-eea3-4527-8f00-4482710e1af3",
6 | "limit": 20,
7 | "filters": {
8 | "isDeletedOnly": false,
9 | "excludeTemplates": false,
10 | "isNavigableOnly": false,
11 | "requireEditPermissions": false,
12 | "ancestors": [],
13 | "createdBy": [],
14 | "editedBy": [],
15 | "lastEditedTime": {},
16 | "createdTime": {}
17 | },
18 | "sort": "Relevance",
19 | "source": "quick_find"
20 | },
21 | "s1": {
22 | "type": "BlocksInAncestor",
23 | "query": "test",
24 | "ancestorId": "78fc5a4b-88d7-4b0e-824e-29407e9f1ec1",
25 | "filters": {
26 | "isDeletedOnly": false,
27 | "excludeTemplates": false,
28 | "isNavigableOnly": false,
29 | "requireEditPermissions": false,
30 | "ancestors": [],
31 | "createdBy": [],
32 | "editedBy": [],
33 | "lastEditedTime": {},
34 | "createdTime": {}
35 | },
36 | "sort": "Relevance",
37 | "limit": 20,
38 | "source": "quick_find_public"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-canonical-page-id.ts:
--------------------------------------------------------------------------------
1 | import { type ExtendedRecordMap } from 'notion-types'
2 |
3 | import { getBlockTitle } from './get-block-title'
4 | import { getPageProperty } from './get-page-property'
5 | import { normalizeTitle } from './normalize-title'
6 | import { uuidToId } from './uuid-to-id'
7 |
8 | /**
9 | * Gets the canonical, display-friendly version of a page's ID for use in URLs.
10 | */
11 | export const getCanonicalPageId = (
12 | pageId: string,
13 | recordMap: ExtendedRecordMap,
14 | { uuid = true }: { uuid?: boolean } = {}
15 | ): string | null => {
16 | if (!pageId || !recordMap) return null
17 |
18 | const id = uuidToId(pageId)
19 | const block = recordMap.block[pageId]?.value
20 |
21 | if (block) {
22 | const slug =
23 | (getPageProperty('slug', block, recordMap) as string | null) ||
24 | (getPageProperty('Slug', block, recordMap) as string | null) ||
25 | normalizeTitle(getBlockTitle(block, recordMap))
26 |
27 | if (slug) {
28 | if (uuid) {
29 | return `${slug}-${id}`
30 | } else {
31 | return slug
32 | }
33 | }
34 | }
35 |
36 | return id
37 | }
38 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-formula.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-file.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-compat/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Client } from '@notionhq/client'
2 |
3 | type ArrayElement =
4 | ArrayType extends readonly (infer ElementType)[] ? ElementType : never
5 |
6 | export type PartialPage = Awaited<
7 | ReturnType['pages']['retrieve']>
8 | >
9 |
10 | export type Page = Extract<
11 | Awaited['pages']['retrieve']>>,
12 | { url: string }
13 | >
14 |
15 | export type PartialBlock = Awaited<
16 | ReturnType['blocks']['retrieve']>
17 | >
18 |
19 | export type Block = Extract
20 |
21 | export type BlockChildren = Awaited<
22 | ReturnType['blocks']['children']['list']>
23 | >['results']
24 |
25 | export type RichText = Extract<
26 | Block,
27 | { type: 'paragraph' }
28 | >['paragraph']['rich_text']
29 | export type RichTextItem = ArrayElement
30 |
31 | export type PageMap = Record
32 | export type BlockMap = Record
33 | export type BlockChildrenMap = Record>
34 |
35 | export type ParentMap = Record
36 |
--------------------------------------------------------------------------------
/examples/full/lib/config.ts:
--------------------------------------------------------------------------------
1 | // TODO: change these to your own values
2 | // NOTE: rootNotionSpaceId is optional; set it to undefined if you don't want to
3 | // use it.
4 | export const rootNotionPageId = '067dd719a912471ea9a3ac10710e7fdf'
5 | export const rootNotionSpaceId = 'fde5ac74-eea3-4527-8f00-4482710e1af3'
6 |
7 | // NOTE: having this enabled can be pretty expensive as it re-generates preview
8 | // images each time a page is built. In a production setting, we recommend that
9 | // you cache the preview image results in a key-value database.
10 | export const previewImagesEnabled = true
11 |
12 | // Whether to use the official public Notion API or the unofficial private API.
13 | // Note that the official API doesn't expose formatting options for many blocks
14 | // and is currently not as well-supported.
15 | // If you want to use the official API, you must provide a NOTION_TOKEN env var.
16 | export const useOfficialNotionAPI =
17 | process.env.USE_OFFICIAL_NOTION_API === 'true' && !!process.env.NOTION_TOKEN
18 |
19 | export const isDev =
20 | process.env.NODE_ENV === 'development' || !process.env.NODE_ENV
21 |
22 | export const port = process.env.PORT || 3000
23 | export const rootDomain = isDev ? `localhost:${port}` : undefined
24 |
--------------------------------------------------------------------------------
/packages/notion-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-utils",
3 | "version": "7.7.3",
4 | "type": "module",
5 | "description": "Useful utilities for working with Notion data. Isomorphic.",
6 | "repository": "NotionX/react-notion-x",
7 | "author": "Travis Fischer ",
8 | "license": "MIT",
9 | "main": "./build/index.js",
10 | "module": "./build/index.js",
11 | "types": "./build/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "types": "./build/index.d.ts",
15 | "default": "./build/index.js"
16 | }
17 | },
18 | "sideEffects": false,
19 | "files": [
20 | "build"
21 | ],
22 | "engines": {
23 | "node": ">=18"
24 | },
25 | "scripts": {
26 | "build": "tsup",
27 | "dev": "tsup --watch",
28 | "watch": "tsup --watch --silent --onSuccess 'echo build successful'",
29 | "clean": "del build",
30 | "test": "run-s test:*",
31 | "test:lint": "eslint .",
32 | "test:typecheck": "tsc --noEmit",
33 | "test:unit": "vitest run"
34 | },
35 | "dependencies": {
36 | "is-url-superb": "catalog:",
37 | "memoize": "catalog:",
38 | "normalize-url": "catalog:",
39 | "notion-types": "workspace:*",
40 | "p-queue": "catalog:"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/notion-compat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-compat",
3 | "version": "7.7.3",
4 | "type": "module",
5 | "description": "Compatibility layer between the official Notion API and unofficial private API.",
6 | "repository": "NotionX/react-notion-x",
7 | "author": "Travis Fischer ",
8 | "license": "MIT",
9 | "main": "./build/index.js",
10 | "module": "./build/index.js",
11 | "types": "./build/index.d.ts",
12 | "sideEffects": false,
13 | "files": [
14 | "build"
15 | ],
16 | "engines": {
17 | "node": ">=18"
18 | },
19 | "scripts": {
20 | "build": "tsup",
21 | "dev": "tsup --watch",
22 | "watch": "tsup --watch --silent --onSuccess 'echo build successful'",
23 | "clean": "del build",
24 | "test": "run-s test:*",
25 | "test:lint": "eslint .",
26 | "test:typecheck": "tsc --noEmit",
27 | "test:unit": "vitest run"
28 | },
29 | "dependencies": {
30 | "notion-types": "workspace:*",
31 | "notion-utils": "workspace:*",
32 | "p-queue": "catalog:"
33 | },
34 | "devDependencies": {
35 | "@notionhq/client": "catalog:",
36 | "notion-client": "workspace:*"
37 | },
38 | "peerDependencies": {
39 | "@notionhq/client": "^2.2.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/equation.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import Katex from '@matejmazur/react-katex'
3 | import { type EquationBlock } from 'notion-types'
4 | import { getBlockTitle } from 'notion-utils'
5 |
6 | import { useNotionContext } from '../context'
7 | import { cs } from '../utils'
8 |
9 | const katexSettings = {
10 | throwOnError: false,
11 | strict: false
12 | }
13 |
14 | export function Equation({
15 | block,
16 | math,
17 | inline = false,
18 | className,
19 | ...rest
20 | }: {
21 | block: EquationBlock
22 | math?: string
23 | inline?: boolean
24 | className?: string
25 | }) {
26 | const { recordMap } = useNotionContext()
27 | math = math || getBlockTitle(block, recordMap)
28 | if (!math) return null
29 |
30 | return (
31 |
40 | {inline ? (
41 |
42 | ) : (
43 |
44 | )}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/packages/notion-types/src/collection.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Color,
3 | type Decoration,
4 | type ID,
5 | type NumberFormat,
6 | type PropertyID,
7 | type PropertyType
8 | } from './core'
9 | import { type Formula } from './formula'
10 |
11 | export interface SelectOption {
12 | id: ID
13 | color: Color
14 | value: string
15 | }
16 |
17 | export interface CollectionPropertySchema {
18 | name: string
19 | type: PropertyType
20 | options?: SelectOption[]
21 | number_format?: NumberFormat
22 | formula?: Formula
23 | }
24 |
25 | export interface CollectionPropertySchemaMap {
26 | [key: string]: CollectionPropertySchema
27 | }
28 |
29 | export interface Collection {
30 | id: ID
31 | version: number
32 | name: Decoration[]
33 | schema: CollectionPropertySchemaMap
34 | icon: string
35 | parent_id: ID
36 | parent_table: string
37 | alive: boolean
38 | copied_from: string
39 | template_pages?: Array
40 |
41 | format?: {
42 | collection_page_properties?: Array<{
43 | property: PropertyID
44 | visible: boolean
45 | }>
46 | property_visibility?: Array<{
47 | property: PropertyID
48 | visibility: 'show' | 'hide'
49 | }>
50 | hide_linked_collection_name?: boolean
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-page-breadcrumbs.ts:
--------------------------------------------------------------------------------
1 | import type * as types from 'notion-types'
2 |
3 | import { getBlockIcon } from './get-block-icon'
4 | import { getBlockParentPage } from './get-block-parent-page'
5 | import { getBlockTitle } from './get-block-title'
6 |
7 | export const getPageBreadcrumbs = (
8 | recordMap: types.ExtendedRecordMap,
9 | activePageId: string
10 | ): Array | null => {
11 | const blockMap = recordMap.block
12 | const breadcrumbs = []
13 |
14 | let currentPageId = activePageId
15 |
16 | do {
17 | const block = blockMap[currentPageId]?.value
18 | if (!block) {
19 | break
20 | }
21 |
22 | const title = getBlockTitle(block, recordMap)
23 | const icon = getBlockIcon(block, recordMap)
24 |
25 | if (!(title || icon)) {
26 | break
27 | }
28 |
29 | breadcrumbs.push({
30 | block,
31 | active: currentPageId === activePageId,
32 | pageId: currentPageId,
33 | title,
34 | icon
35 | })
36 |
37 | const parentBlock = getBlockParentPage(block, recordMap)
38 | const parentId = parentBlock?.id
39 |
40 | if (!parentId) {
41 | break
42 | }
43 |
44 | currentPageId = parentId
45 | } while (true)
46 |
47 | breadcrumbs.reverse()
48 |
49 | return breadcrumbs
50 | }
51 |
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/blocks/file.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "d142a056-f104-477c-96d5-2b03fdd8c6e3",
3 | "version": 50,
4 | "type": "file",
5 | "properties": {
6 | "size": [["1605.6KB"]],
7 | "title": [["Aaron_Wang_Design_Resume.pdf"]],
8 | "source": [
9 | [
10 | "https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f428f70f-aadc-45e3-acdc-72f762c2fbc9/Aaron_Wang_Design_Resume.pdf"
11 | ]
12 | ]
13 | },
14 | "created_time": 1589838779607,
15 | "last_edited_time": 1595261160000,
16 | "parent_id": "a779c1d6-9399-4cdf-beee-2a6fbcf9340c",
17 | "parent_table": "block",
18 | "alive": true,
19 | "file_ids": [
20 | "d325a6d0-28b8-41e0-9285-1259e2dcbbce",
21 | "076e9b76-e457-4c79-9885-d5df1a60bf24",
22 | "9ee96f80-b8fd-4f73-8518-c645bb760b5a",
23 | "e51498ba-3b60-41fb-b111-10c0f878bc6d",
24 | "1dec7864-81a3-4454-9db6-4c6f998ea0a4",
25 | "ee750ab8-c198-4d98-aba5-269108eca1df",
26 | "f428f70f-aadc-45e3-acdc-72f762c2fbc9"
27 | ],
28 | "created_by_table": "notion_user",
29 | "created_by_id": "27f1b8d4-257c-46b0-bb74-37b8e5712699",
30 | "last_edited_by_table": "notion_user",
31 | "last_edited_by_id": "27f1b8d4-257c-46b0-bb74-37b8e5712699",
32 | "shard_id": 759545,
33 | "space_id": "053d69ca-7a30-40b4-9e39-7fb0f92b96f9"
34 | }
35 |
--------------------------------------------------------------------------------
/packages/notion-types/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # notion-types
6 |
7 | > TypeScript types for core Notion data structures.
8 |
9 | [](https://www.npmjs.com/package/notion-types) [](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml) [](https://prettier.io)
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install notion-types
15 | ```
16 |
17 | This package only exports types and is compatible with both Node.js and browsers.
18 |
19 | ## Usage
20 |
21 | ```ts
22 | import * as notion from 'notion-types'
23 | ```
24 |
25 | ## Docs
26 |
27 | See the [full docs](https://github.com/NotionX/react-notion-x).
28 |
29 | ## License
30 |
31 | MIT © [Travis Fischer](https://transitivebullsh.it)
32 |
33 | Support my OSS work by following me on twitter
34 |
--------------------------------------------------------------------------------
/examples/full/pages/index.tsx:
--------------------------------------------------------------------------------
1 | // import { type ExtendedRecordMap } from 'notion-types'
2 |
3 | // import { NotionPage } from '../components/NotionPage'
4 | // import {
5 | // previewImagesEnabled,
6 | // rootDomain,
7 | // rootNotionPageId
8 | // } from '../lib/config'
9 | // import * as notion from '../lib/notion'
10 |
11 | // export const getStaticProps = async () => {
12 | // const pageId = rootNotionPageId
13 | // const recordMap = await notion.getPage(pageId)
14 |
15 | // return {
16 | // props: {
17 | // recordMap
18 | // },
19 | // revalidate: 86_400 // cache for 1 day in seconds
20 | // }
21 | // }
22 |
23 | // export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) {
24 | // return (
25 | //
31 | // )
32 | // }
33 |
34 | export const getStaticProps = async () => {
35 | return { props: {}, revalidate: false }
36 | }
37 |
38 | export default function Page() {
39 | return (
40 |
41 | Hey 👋 I've disabled the public demo for react-notion-x for now because my
42 | Vercel bill keeps increasing due to people abusing the demo.
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/get-block-parent-page.ts:
--------------------------------------------------------------------------------
1 | import type * as types from 'notion-types'
2 |
3 | /**
4 | * Returns the parent page block containing a given page.
5 | *
6 | * Note that many times this will not be the direct parent block since
7 | * some non-page content blocks can contain sub-blocks.
8 | */
9 | export const getBlockParentPage = (
10 | block: types.Block,
11 | recordMap: types.ExtendedRecordMap,
12 | {
13 | inclusive = false
14 | }: {
15 | inclusive?: boolean
16 | } = {}
17 | ): types.PageBlock | null => {
18 | let currentRecord: types.Block | types.Collection | undefined = block
19 |
20 | while (currentRecord) {
21 | if (inclusive && (currentRecord as types.Block)?.type === 'page') {
22 | return currentRecord as types.PageBlock
23 | }
24 |
25 | const parentId: string = currentRecord.parent_id
26 | const parentTable = currentRecord.parent_table
27 |
28 | if (!parentId) {
29 | break
30 | }
31 |
32 | if (parentTable === 'collection') {
33 | currentRecord = recordMap.collection[parentId]?.value
34 | } else {
35 | currentRecord = recordMap.block[parentId]?.value
36 |
37 | if ((currentRecord as types.Block)?.type === 'page') {
38 | return currentRecord as types.PageBlock
39 | }
40 | }
41 | }
42 |
43 | return null
44 | }
45 |
--------------------------------------------------------------------------------
/examples/minimal/pages/index.tsx:
--------------------------------------------------------------------------------
1 | // import { type ExtendedRecordMap } from 'notion-types'
2 |
3 | // import { NotionPage } from '../components/NotionPage'
4 | // import { rootNotionPageId } from '../lib/config'
5 | // import notion from '../lib/notion'
6 |
7 | // export const getStaticProps = async () => {
8 | // const pageId = rootNotionPageId
9 | // const recordMap = await notion.getPage(pageId)
10 |
11 | // return {
12 | // props: {
13 | // recordMap
14 | // },
15 | // // cache for 1 day in seconds
16 | // // NOTE: you'll likely want to use a shorter cache time for your app, but
17 | // // I'm bumping this up because my vercel bill keeps increasing due to people
18 | // // abusing the demo to host their own sites.
19 | // revalidate: 86_400
20 | // }
21 | // }
22 |
23 | // export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) {
24 | // return
25 | // }
26 |
27 | export const getStaticProps = async () => {
28 | return { props: {}, revalidate: false }
29 | }
30 |
31 | export default function Page() {
32 | return (
33 |
34 | Hey 👋 I've disabled the public demo for react-notion-x for now because my
35 | Vercel bill keeps increasing due to people abusing the demo.
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-auto-increment-id.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-1d79cc15-d040-43eb-9d65-7e35bafbcf74.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "e3bf5e62-9eae-449f-a777-175e1def8b80",
5 | "159ba45a-670d-4a0d-bfd2-d54146e72740",
6 | "bf7af6a2-79f0-44f0-9355-0099157e7883",
7 | "1ef2791e-d8a4-4234-8327-ba0bc066bf66",
8 | "e8d5499c-2b14-4501-90db-973142cc3b28",
9 | "f2c125eb-97d0-40fa-b147-d325b8689f99",
10 | "1b47d758-678d-44bf-8fe4-e432618b6282",
11 | "2ccbad8c-b6ce-4092-b56e-67556e19834e",
12 | "dc0be7cc-c8a7-4197-9498-b80b0af2293f",
13 | "95b21335-9403-4491-b07e-dc7aa7b4831b",
14 | "1f9fa735-a6ff-481c-aa72-0d824660dd36",
15 | "0f57d9f2-b687-4a7a-839f-8ea642c1b34e",
16 | "668439ae-81b2-4f07-802c-4c2dc2109a31",
17 | "9cbf50b4-1237-47a4-affa-332d83ada494",
18 | "61f6ec91-aac3-4211-bd15-f6e85e3800bd",
19 | "ffc132d9-6f51-4100-8de8-ffd44f07bb1f",
20 | "e9ba7138-c112-4ffc-9a49-58cc79ca97ba",
21 | "1e03f644-396f-42ba-addb-3b1dbf2a513c",
22 | "8f01a708-e394-40c0-8c97-48c93124fe27",
23 | "e250707e-beee-4f50-a9f4-01ea03186929",
24 | "78823d5c-e17f-4f51-a648-a77626259cdd",
25 | "462b6820-aaf3-4cac-8a24-c961e2eb57b0",
26 | "1fb18673-c269-41dc-9588-2adc35ebdc53",
27 | "36d2d68c-71da-44ba-8421-b7e57e34bb07"
28 | ],
29 | "aggregationResults": [],
30 | "total": 24
31 | }
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-number.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-auto-increment-id.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeAutoIncrementId(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeAutoIncrementId
12 |
--------------------------------------------------------------------------------
/examples/full/lib/notion.ts:
--------------------------------------------------------------------------------
1 | import { Client } from '@notionhq/client'
2 | import { NotionAPI } from 'notion-client'
3 | import { NotionCompatAPI } from 'notion-compat'
4 | import {
5 | type ExtendedRecordMap,
6 | type SearchParams,
7 | type SearchResults
8 | } from 'notion-types'
9 |
10 | import { previewImagesEnabled, useOfficialNotionAPI } from './config'
11 | import { getPreviewImageMap } from './preview-images'
12 |
13 | const notion = useOfficialNotionAPI
14 | ? new NotionCompatAPI(new Client({ auth: process.env.NOTION_TOKEN }))
15 | : new NotionAPI()
16 |
17 | if (useOfficialNotionAPI) {
18 | console.warn(
19 | 'Using the official Notion API. Note that many blocks only include partial support for formatting and layout. Use at your own risk.'
20 | )
21 | }
22 |
23 | export async function getPage(pageId: string): Promise {
24 | const recordMap = await notion.getPage(pageId, { fetchRelationPages: true })
25 |
26 | if (previewImagesEnabled) {
27 | const previewImageMap = await getPreviewImageMap(recordMap)
28 | ;(recordMap as any).preview_images = previewImageMap
29 | }
30 |
31 | return recordMap
32 | }
33 |
34 | export async function search(params: SearchParams): Promise {
35 | if ('search' in notion) {
36 | return notion.search(params)
37 | } else {
38 | throw new Error('Notion API does not support search')
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/examples/minimal/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
6 |
7 | ## Intro
8 |
9 | This is a minimal Next.js example project using `react-notion-x`, with the most important code in [`pages/[pageId].tsx`](./pages/%5BpageId%5D.tsx) and [`components/NotionPage.tsx`](./components/NotionPage.tsx). You can view this example [live on Vercel](https://react-notion-x-minimal-demo.transitivebullsh.it).
10 |
11 | Config can be found in [`lib/config.ts`](./lib/config.ts)
12 |
13 | ## Getting Started
14 |
15 | First, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | yarn dev
21 | ```
22 |
23 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
24 |
25 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
26 |
27 | ## License
28 |
29 | MIT © [Travis Fischer](https://transitivebullsh.it)
30 |
31 | Support my OSS work by following me on twitter
32 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-url.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-email.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeEmail(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeEmail
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-timestamp.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/notion-client/fixtures/blocks/drive.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "8d6d9aa0-7baa-4fdb-9028-ffc5fdaa2224",
3 | "version": 5,
4 | "type": "drive",
5 | "format": {
6 | "drive_status": {
7 | "authed": true,
8 | "last_fetched": 1597765319949
9 | },
10 | "drive_properties": {
11 | "url": "https://drive.google.com/file/d/1QqtwutJ9KC5gGitlHymkgVwJvR-l4n9S/view?usp=drivesdk",
12 | "icon": "https://drive-thirdparty.googleusercontent.com/64/type/application/pdf",
13 | "title": "Aaron_Wang_Design_Resume.pdf",
14 | "file_id": "1QqtwutJ9KC5gGitlHymkgVwJvR-l4n9S",
15 | "trashed": false,
16 | "version": "3",
17 | "thumbnail": "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/00f7e6c4-ae90-40b0-bb73-8a639cb88efb/1QqtwutJ9KC5gGitlHymkgVwJvR-l4n9S.png",
18 | "user_name": "Aaron Wang",
19 | "modified_time": 1597176769000
20 | }
21 | },
22 | "created_time": 1597765399988,
23 | "last_edited_time": 1597765380000,
24 | "parent_id": "5d4e290c-a460-4d8f-b809-af806a6c1749",
25 | "parent_table": "block",
26 | "alive": true,
27 | "copied_from": "bfb81d08-0d84-42e9-84dd-3e7fee669684",
28 | "file_ids": ["00f7e6c4-ae90-40b0-bb73-8a639cb88efb"],
29 | "created_by_table": "notion_user",
30 | "created_by_id": "27f1b8d4-257c-46b0-bb74-37b8e5712699",
31 | "last_edited_by_table": "notion_user",
32 | "last_edited_by_id": "db401f86-4012-4d18-9445-236978ef32df",
33 | "shard_id": 924403,
34 | "space_id": "fde5ac74-eea3-4527-8f00-4482710e1af3"
35 | }
36 |
--------------------------------------------------------------------------------
/packages/notion-utils/fixtures/nba/data/1a425309-21c0-4ca1-8548-c5915137b566-43d091fb-a648-40cc-ae10-9d9784c34524.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "table",
3 | "blockIds": [
4 | "dfc56d92-0eb1-4da1-a9c4-7079b85a65c3",
5 | "ec525e26-c898-49a5-b930-de3ab98eedb4",
6 | "041482e3-a3a4-41c4-a30d-38f1621a45f8",
7 | "040d887f-7e12-4041-85d0-2f01a651e4a6",
8 | "4c6173b9-d127-48e5-b06c-5b1199e1b668",
9 | "e8afe3d1-89bc-4304-99c3-1432ac893c35",
10 | "7ed2188a-e376-45ad-b032-ce4b72129c6d",
11 | "1cb564d0-2918-4625-8c82-1812df38e104",
12 | "f5a4d04f-31e7-4d10-99f3-bd90b6fac5e8",
13 | "f76af635-59d6-4321-a1a5-254314c82abf",
14 | "0dbd808e-acde-46af-b341-c509726914bb",
15 | "d6c06029-acde-44e1-836f-e1486ebede6e",
16 | "5903bd9d-026a-41b2-ae39-58c7348a9167",
17 | "65af3799-3b7e-4fc2-8dce-ba2c520f6bd6",
18 | "5d07bfbd-fd51-4cab-867b-39d67061c193",
19 | "453c7312-d23c-4a09-8f7f-019931287567",
20 | "5267f716-9b46-4784-81cc-b6227f932ccb",
21 | "60e2f725-8a98-4c9f-826f-5c989dee34e7",
22 | "e652949a-bef8-43f1-baa9-2d760b23deb1",
23 | "effaf7b4-d44f-4f76-87fb-ddb55e4f235d",
24 | "4721be6b-2e37-484e-b7ab-78eda2ac1fc8",
25 | "88e820c8-cf3a-483c-b70a-f95a1de175ba",
26 | "36249588-5867-4eab-a5d8-44479a1d7531",
27 | "fb7e8bc2-1c6c-4bb9-8417-f3f35ec81c40",
28 | "f3f7f355-52a9-4931-b7d2-19b637b6ed02",
29 | "bad715e5-9ed4-4105-894a-54dbb9cb6731",
30 | "4eb264b8-df87-49f0-a0d1-af56908d0bb5",
31 | "766b743c-1aa6-468f-b94c-a8d78c27d78e"
32 | ],
33 | "aggregationResults": [],
34 | "total": 28
35 | }
--------------------------------------------------------------------------------
/packages/notion-utils/src/normalize-url.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 |
3 | import { normalizeUrl } from './normalize-url'
4 |
5 | test('normalizeUrl invalid', () => {
6 | expect(normalizeUrl()).toBe('')
7 | expect(normalizeUrl('')).toBe('')
8 | expect(normalizeUrl('#')).toBe('')
9 | expect(normalizeUrl('#foo')).toBe('')
10 | expect(normalizeUrl('/foo')).toBe('')
11 | expect(normalizeUrl('/foo/bar')).toBe('')
12 | expect(normalizeUrl('://test.com')).toBe('')
13 | })
14 |
15 | test('normalizeUrl valid', () => {
16 | const fixtures = [
17 | 'test.com',
18 | 'test.com/123',
19 | '//test.com',
20 | 'https://test.com',
21 | 'https://www.test.com',
22 | 'https://test.com/foo/bar',
23 | 'https://test.com/foo/bar/',
24 | 'https://test.com/foo/bar?foo=bar&cat=dog',
25 | 'https://www.notion.so/image/https%3A%2F%2Fs3.us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fae16c287-668f-4ea7-90a8-5ed96302e14f%2Fquilt-opt.jpg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DAKIAT73L2G45EIPT3X45%252F20220327%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20220327T124856Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3Dfdaa47d722db4b4052267d999003c6392bbd3d8c4169268b202b8268b2af12ab%26X-Amz-SignedHeaders%3Dhost%26x-id%3DGetObject?table=block&id=ddec4f2d-6afa-498f-8141-405647e02ea5&cache=v2'
26 | ]
27 |
28 | for (const url of fixtures) {
29 | const normalizedUrl = normalizeUrl(url)
30 | expect(normalizedUrl).toBeTruthy()
31 | expect(normalizedUrl).toMatchSnapshot()
32 | }
33 | })
34 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/file.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import { type FileBlock } from 'notion-types'
3 |
4 | import { useNotionContext } from '../context'
5 | import { FileIcon } from '../icons/file-icon'
6 | import { cs } from '../utils'
7 | import { Text } from './text'
8 |
9 | export function File({
10 | block,
11 | className
12 | }: {
13 | block: FileBlock
14 | className?: string
15 | }) {
16 | const { components, recordMap } = useNotionContext()
17 |
18 | let source =
19 | recordMap.signed_urls[block.id] || block.properties?.source?.[0]?.[0]
20 |
21 | if (!source) {
22 | return null
23 | }
24 |
25 | if (block.space_id) {
26 | const url = new URL(source)
27 | url.searchParams.set('spaceId', block.space_id)
28 | source = url.toString()
29 | }
30 |
31 | return (
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {block.properties?.size && (
47 |
48 |
49 |
50 | )}
51 |
52 |
53 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - packages/*
3 | - examples/*
4 |
5 | catalog:
6 | '@fisch0920/config': ^1.2.0
7 | '@fisch0920/eslint-config': ^1.4.0
8 | '@fisch0920/medium-zoom': ^1.0.7
9 | '@matejmazur/react-katex': ^3.1.3
10 | '@notionhq/client': ^2.3.0
11 | '@total-typescript/ts-reset': ^0.6.1
12 | '@types/lodash.throttle': ^4.1.6
13 | '@types/node': ^24.5.1
14 | '@types/prismjs': ^1.26.5
15 | '@types/react': ^19.2.7
16 | '@types/react-dom': ^19.2.3
17 | '@types/react-modal': ^3.16.3
18 | bumpp: ^10.1.1
19 | classnames: ^2.5.1
20 | clipboard-copy: ^4.0.1
21 | cross-env: ^7.0.3
22 | date-fns: ^4.1.0
23 | del-cli: ^6.0.0
24 | eslint: ^9.35.0
25 | format-number: ^3.0.0
26 | is-url-superb: ^6.1.0
27 | katex: 0.16.21
28 | ky: ^1.8.1
29 | lodash.throttle: ^4.1.1
30 | lqip-modern: ^2.1.0
31 | memoize: ^10.1.0
32 | next: ^16.0.10
33 | normalize-url: ^8.0.1
34 | npm-run-all2: ^8.0.4
35 | ofetch: ^1.4.1
36 | p-map: ^7.0.3
37 | p-memoize: ^7.1.1
38 | p-queue: ^8.1.0
39 | prettier: ^3.6.2
40 | prismjs: ^1.30.0
41 | react: ^19.2.3
42 | react-dom: ^19.2.3
43 | react-fast-compare: ^3.2.0
44 | react-hotkeys-hook: ^4.5.1
45 | react-image: ^4.0.3
46 | react-intersection-observer: ^9.16.0
47 | react-modal: ^3.16.3
48 | react-scripts: 5.0.1
49 | react-tweet-embed: ^2.0.0
50 | simple-git-hooks: ^2.13.0
51 | tsup: ^8.5.0
52 | tsx: ^4.20.5
53 | turbo: ^2.5.6
54 | typescript: ^5.9.2
55 | unionize: ^3.1.0
56 | vitest: ^3.2.4
57 |
58 | enablePrePostScripts: true
59 |
60 | minimumReleaseAge: 1440
61 |
62 | onlyBuiltDependencies:
63 | - simple-git-hooks
64 |
65 | packageManagerStrict: false
66 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-person.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/packages/notion-utils/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # notion-utils
6 |
7 | > Useful utilities for working with Notion data. Isomorphic.
8 |
9 | [](https://www.npmjs.com/package/notion-utils) [](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml) [](https://prettier.io)
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install notion-utils
15 | ```
16 |
17 | This package is compatible with both Node.js and client-side web usage.
18 |
19 | ## Usage
20 |
21 | ```ts
22 | import { parsePageId } from 'notion-utils'
23 |
24 | parsePageId(
25 | 'https://www.notion.so/Notion-Tests-067dd719a912471ea9a3ac10710e7fdf'
26 | )
27 | // '067dd719-a912-471e-a9a3-ac10710e7fdf'
28 |
29 | parsePageId('About-d9ae0c6e7cad49a78e21d240cf2e3d04')
30 | // 'd9ae0c6e-7cad-49a7-8e21-d240cf2e3d04'
31 |
32 | parsePageId('About-d9ae0c6e7cad49a78e21d240cf2e3d04', { uuid: false })
33 | // 'd9ae0c6e7cad49a78e21d240cf2e3d04'
34 | ```
35 |
36 | ## Docs
37 |
38 | See the [full docs](https://github.com/NotionX/react-notion-x).
39 |
40 | ## License
41 |
42 | MIT © [Travis Fischer](https://transitivebullsh.it)
43 |
44 | Support my OSS work by following me on twitter
45 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/third-party/collection-utils.ts:
--------------------------------------------------------------------------------
1 | import { format } from 'date-fns/format'
2 |
3 | export function getCollectionGroups(
4 | collection: any,
5 | collectionView: any,
6 | collectionData: any,
7 | ...rest: any[]
8 | ): any[] {
9 | const elems = collectionView?.format?.collection_groups || []
10 | return elems.map(({ property, hidden, value: { value, type } }: any) => {
11 | const isUncategorizedValue = value === undefined
12 | const isDateValue = value?.range
13 | // TODO: review dates reducers
14 | const queryLabel = isUncategorizedValue
15 | ? 'uncategorized'
16 | : isDateValue
17 | ? value.range?.start_date || value.range?.end_date
18 | : value?.value || value
19 |
20 | const collectionGroup = collectionData[`results:${type}:${queryLabel}`]
21 | let queryValue =
22 | !isUncategorizedValue && (isDateValue || value?.value || value)
23 | let schema = collection.schema[property]
24 |
25 | // Checkbox boolen value must be Yes||No
26 | if (type === 'checkbox' && value) {
27 | queryValue = 'Yes'
28 | }
29 |
30 | if (isDateValue) {
31 | schema = {
32 | type: 'text',
33 | name: 'text'
34 | }
35 |
36 | // TODO: review dates format based on value.type ('week'|'month'|'year')
37 | queryValue = format(new Date(queryLabel), 'MMM d, yyyy hh:mm aa')
38 | }
39 |
40 | return {
41 | collectionGroup,
42 | schema,
43 | value: queryValue || 'No description',
44 | hidden,
45 | collection,
46 | collectionView,
47 | collectionData,
48 | blockIds: collectionGroup?.blockIds,
49 | ...rest
50 | }
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/loading-icon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { cs } from '../utils'
4 |
5 | export function LoadingIcon(props: any) {
6 | const { className, ...rest } = props
7 | return (
8 |
9 |
10 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
37 |
43 |
52 |
53 |
54 |
55 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion",
3 | "private": true,
4 | "description": "Fast and accurate React renderer for Notion. TS batteries included.",
5 | "repository": "NotionX/react-notion-x",
6 | "author": "Travis Fischer ",
7 | "license": "MIT",
8 | "version": "7.7.3",
9 | "packageManager": "pnpm@10.26.0",
10 | "engines": {
11 | "node": ">=20"
12 | },
13 | "type": "module",
14 | "scripts": {
15 | "build": "turbo build --filter='./packages/*'",
16 | "dev": "turbo dev --concurrency 50 --continue --filter='./packages/*'",
17 | "clean": "turbo clean",
18 | "test": "turbo test",
19 | "test:format": "prettier --check \"**/*.{js,ts,tsx}\"",
20 | "test:lint": "turbo test:lint",
21 | "test:typecheck": "turbo test:typecheck",
22 | "test:unit": "turbo test:unit",
23 | "release": "bumpp -r && pnpm publish -r",
24 | "pretest": "run-s build",
25 | "prepare": "simple-git-hooks"
26 | },
27 | "devDependencies": {
28 | "@fisch0920/config": "catalog:",
29 | "@total-typescript/ts-reset": "catalog:",
30 | "@types/node": "catalog:",
31 | "bumpp": "catalog:",
32 | "del-cli": "catalog:",
33 | "eslint": "catalog:",
34 | "npm-run-all2": "catalog:",
35 | "prettier": "catalog:",
36 | "react": "catalog:",
37 | "react-dom": "catalog:",
38 | "simple-git-hooks": "catalog:",
39 | "tsup": "catalog:",
40 | "tsx": "catalog:",
41 | "turbo": "catalog:",
42 | "typescript": "catalog:",
43 | "vitest": "catalog:"
44 | },
45 | "prettier": "@fisch0920/config/prettier",
46 | "simple-git-hooks": {
47 | "pre-commit": "npx lint-staged"
48 | },
49 | "lint-staged": {
50 | "*.{ts,tsx}": [
51 | "prettier --ignore-unknown --write",
52 | "eslint --fix"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/property-icon.tsx:
--------------------------------------------------------------------------------
1 | import { type PropertyType } from 'notion-types'
2 |
3 | import AutoIncrementIdIcon from './type-auto-increment-id'
4 | import CheckboxIcon from './type-checkbox'
5 | import DateIcon from './type-date'
6 | import EmailIcon from './type-email'
7 | import FileIcon from './type-file'
8 | import FormulaIcon from './type-formula'
9 | import MultiSelectIcon from './type-multi-select'
10 | import NumberIcon from './type-number'
11 | import PersonIcon from './type-person'
12 | import Person2Icon from './type-person-2'
13 | import PhoneNumberIcon from './type-phone-number'
14 | import RelationIcon from './type-relation'
15 | import SelectIcon from './type-select'
16 | import StatusIcon from './type-status'
17 | import TextIcon from './type-text'
18 | import TimestampIcon from './type-timestamp'
19 | import TitleIcon from './type-title'
20 | import UrlIcon from './type-url'
21 |
22 | interface PropertyIconProps {
23 | className?: string
24 | type: PropertyType
25 | }
26 |
27 | const iconMap = {
28 | title: TitleIcon,
29 | text: TextIcon,
30 | number: NumberIcon,
31 | select: SelectIcon,
32 | status: StatusIcon,
33 | multi_select: MultiSelectIcon,
34 | date: DateIcon,
35 | person: PersonIcon,
36 | file: FileIcon,
37 | checkbox: CheckboxIcon,
38 | url: UrlIcon,
39 | email: EmailIcon,
40 | phone_number: PhoneNumberIcon,
41 | formula: FormulaIcon,
42 | relation: RelationIcon,
43 | created_time: TimestampIcon,
44 | last_edited_time: TimestampIcon,
45 | created_by: Person2Icon,
46 | last_edited_by: Person2Icon,
47 | auto_increment_id: AutoIncrementIdIcon
48 | }
49 |
50 | export function PropertyIcon({ type, ...rest }: PropertyIconProps) {
51 | const icon = iconMap[type] as any
52 | if (!icon) return null
53 |
54 | return icon(rest)
55 | }
56 |
--------------------------------------------------------------------------------
/examples/full/components/LoadingIcon.tsx:
--------------------------------------------------------------------------------
1 | import cs from 'classnames'
2 |
3 | import styles from './styles.module.css'
4 |
5 | export function LoadingIcon(props: any) {
6 | const { className, ...rest } = props
7 | return (
8 |
13 |
14 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
41 |
47 |
56 |
57 |
58 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/packages/notion-client/src/notion-api.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 |
3 | import { NotionAPI } from './notion-api'
4 |
5 | const pageIdFixturesSuccess = [
6 | '78fc5a4b88d74b0e824e29407e9f1ec1',
7 | '067dd719-a912-471e-a9a3-ac10710e7fdf',
8 | '067dd719a912471ea9a3ac10710e7fdf',
9 | 'https://www.notion.so/saasifysh/Embeds-5d4e290ca4604d8fb809af806a6c1749',
10 | 'https://www.notion.so/saasifysh/File-Uploads-34d650c65da34f888335dbd3ddd141dc',
11 | 'Color-Rainbow-54bf56611797480c951e5c1f96cb06f2',
12 | 'e68c18a461904eb5a2ddc3748e76b893',
13 | 'https://www.notion.so/saasifysh/Saasify-Key-Takeaways-689a8abc1afa4699905aa2f2e585e208',
14 | 'https://www.notion.so/saasifysh/TransitiveBullsh-it-78fc5a4b88d74b0e824e29407e9f1ec1',
15 | 'https://www.notion.so/saasifysh/About-8d0062776d0c4afca96eb1ace93a7538',
16 | 'https://www.notion.so/potionsite/newest-board-a899b98b7cdc424585e5ddebbdae60cc'
17 |
18 | // collections stress test
19 | // NOTE: removing because of sporadic timeouts
20 | // 'nba-3f92ae505636427c897634a15b9f2892'
21 | ]
22 |
23 | const pageIdFixturesFailure = [
24 | 'bdecdf150d0e40cb9f3412be132335d4', // private page
25 | 'foo' // invalid page id
26 | ]
27 |
28 | for (const pageId of pageIdFixturesSuccess) {
29 | test(
30 | `NotionAPI.getPage success ${pageId}`,
31 | {
32 | timeout: 60_000 // one minute timeout
33 | },
34 | async () => {
35 | const api = new NotionAPI()
36 | const page = await api.getPage(pageId, { throwOnCollectionErrors: true })
37 |
38 | expect(page).toBeTruthy()
39 | expect(page.block).toBeTruthy()
40 | }
41 | )
42 | }
43 |
44 | for (const pageId of pageIdFixturesFailure) {
45 | test(`NotionAPI.getPage failure ${pageId}`, async () => {
46 | const api = new NotionAPI()
47 | await expect(() => api.getPage(pageId)).rejects.toThrow()
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/page-title.tsx:
--------------------------------------------------------------------------------
1 | import { type Block, type Decoration } from 'notion-types'
2 | import { getBlockTitle } from 'notion-utils'
3 | import React from 'react'
4 |
5 | import { useNotionContext } from '../context'
6 | import { cs } from '../utils'
7 | import { PageIcon } from './page-icon'
8 | import { Text } from './text'
9 |
10 | export function PageTitleImpl({
11 | block,
12 | className,
13 | defaultIcon,
14 | ...rest
15 | }: {
16 | block: Block
17 | className?: string
18 | defaultIcon?: string | null
19 | }) {
20 | const { recordMap } = useNotionContext()
21 |
22 | if (!block) return null
23 |
24 | if (
25 | block.type === 'collection_view_page' ||
26 | block.type === 'collection_view'
27 | ) {
28 | const title = getBlockTitle(block, recordMap)
29 | if (!title) {
30 | return null
31 | }
32 |
33 | const titleDecoration: Decoration[] = [[title]]
34 |
35 | return (
36 |
37 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | if (!block.properties?.title) {
51 | return null
52 | }
53 |
54 | return (
55 |
56 |
61 |
62 |
63 |
64 |
65 |
66 | )
67 | }
68 |
69 | export const PageTitle = React.memo(PageTitleImpl)
70 |
--------------------------------------------------------------------------------
/packages/notion-compat/src/notion-compat-api.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 |
3 | test('dummy', async () => {
4 | // dummy
5 | expect(true).toBe(true)
6 | })
7 |
8 | /*
9 | TODO: this test currently fails because of an [esbuild issue](https://github.com/evanw/esbuild/issues/1921):
10 |
11 | ```
12 | Uncaught exception in src/notion-compat-api.test.ts
13 |
14 | Error: Dynamic require of "events" is not supported
15 |
16 | › file:///Users/tfischer/dev/modules/react-notion-x/packages/notion-client/build/index.js:1:382
17 | › file:///Users/tfischer/dev/modules/react-notion-x/node_modules/cacheable-request/src/index.js:3:22
18 | › file:///Users/tfischer/dev/modules/react-notion-x/packages/notion-client/build/index.js:1:462
19 | › file:///Users/tfischer/dev/modules/react-notion-x/node_modules/got/dist/source/core/index.js:7:30
20 | ```
21 | */
22 |
23 | // import { Client } from '@notionhq/client'
24 | // import { promises as fs } from 'fs'
25 | // import { NotionAPI } from 'notion-client'
26 |
27 | // import { NotionCompatAPI } from './notion-compat-api'
28 |
29 | // const debug = false
30 |
31 | // test('NotionCompatAPI', async () => {
32 | // // const pageId = '067dd719a912471ea9a3ac10710e7fdf'
33 | // const pageId = '8bcd65801a5d450fb7218d8890a38c29'
34 |
35 | // const auth = 'secret_KZ8vNH8UmOGIEQTlcPOp19yAiy0JZbyEqN5mLSqz2HF'
36 |
37 | // const client = new Client({ auth })
38 | // const compatAPI = new NotionCompatAPI(client)
39 | // const api = new NotionAPI()
40 |
41 | // const page = await api.getPage(pageId)
42 | // const compatPage = await compatAPI.getPage(pageId)
43 |
44 | // t.truthy(page)
45 | // t.truthy(compatPage)
46 |
47 | // if (debug) {
48 | // await fs.writeFile(`${pageId}.json`, JSON.stringify(page, null, 2))
49 | // await fs.writeFile(
50 | // `${pageId}.compat.json`,
51 | // JSON.stringify(compatPage, null, 2)
52 | // )
53 | // }
54 | // })
55 |
--------------------------------------------------------------------------------
/examples/cra/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
30 | React Notion X CRA Demo
31 |
32 |
33 |
34 | You need to enable JavaScript to run this app.
35 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/collection-view-calendar.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgCollectionViewCalendar(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgCollectionViewCalendar
12 |
--------------------------------------------------------------------------------
/examples/full/lib/preview-images.ts:
--------------------------------------------------------------------------------
1 | import ky from 'ky'
2 | import lqip from 'lqip-modern'
3 | import {
4 | type ExtendedRecordMap,
5 | type PreviewImage,
6 | type PreviewImageMap
7 | } from 'notion-types'
8 | import { defaultMapImageUrl, getPageImageUrls } from 'notion-utils'
9 | import pMap from 'p-map'
10 | import pMemoize from 'p-memoize'
11 |
12 | // NOTE: this is just an example of how to pre-compute preview images.
13 | // Depending on how many images you're working with, this can potentially be
14 | // very expensive to recompute, so in production we recommend that you cache
15 | // the preview image results in a key-value database of your choosing.
16 | // If you're not sure where to start, check out https://github.com/jaredwray/keyv
17 |
18 | export async function getPreviewImageMap(
19 | recordMap: ExtendedRecordMap
20 | ): Promise {
21 | const urls: string[] = getPageImageUrls(recordMap, {
22 | mapImageUrl: defaultMapImageUrl
23 | })
24 |
25 | const previewImagesMap = Object.fromEntries(
26 | await pMap(urls, async (url) => [url, await getPreviewImage(url)], {
27 | concurrency: 8
28 | })
29 | )
30 |
31 | return previewImagesMap
32 | }
33 |
34 | async function createPreviewImage(url: string): Promise {
35 | try {
36 | const body = await ky(url).arrayBuffer()
37 | const result = await lqip(body)
38 | console.log('lqip', { originalUrl: url, ...result.metadata })
39 |
40 | return {
41 | originalWidth: result.metadata.originalWidth,
42 | originalHeight: result.metadata.originalHeight,
43 | dataURIBase64: result.metadata.dataURIBase64
44 | }
45 | } catch (err: any) {
46 | if (err.message === 'Input buffer contains unsupported image format') {
47 | return null
48 | }
49 |
50 | console.warn('failed to create preview image', url, err.message)
51 | return null
52 | }
53 | }
54 |
55 | export const getPreviewImage = pMemoize(createPreviewImage)
56 |
--------------------------------------------------------------------------------
/packages/notion-utils/src/map-image-url.ts:
--------------------------------------------------------------------------------
1 | import { type Block } from 'notion-types'
2 |
3 | // eslint-disable-next-line security/detect-unsafe-regex
4 | const GIF_REGEXP = /(?:https?:\/\/)?[^\s]+\.gif(?=$|\?|#)/
5 |
6 | export const defaultMapImageUrl = (
7 | url: string | undefined,
8 | block: Block
9 | ): string | undefined => {
10 | if (!url) {
11 | return undefined
12 | }
13 |
14 | if (url.startsWith('data:')) {
15 | return url
16 | }
17 |
18 | if (GIF_REGEXP.test(url)) {
19 | return url
20 | }
21 |
22 | // more recent versions of notion don't proxy unsplash images
23 | if (url.startsWith('https://images.unsplash.com')) {
24 | return url
25 | }
26 |
27 | try {
28 | const u = new URL(url)
29 |
30 | if (
31 | u.pathname.startsWith('/secure.notion-static.com') &&
32 | u.hostname.endsWith('.amazonaws.com')
33 | ) {
34 | if (
35 | u.searchParams.has('X-Amz-Credential') &&
36 | u.searchParams.has('X-Amz-Signature') &&
37 | u.searchParams.has('X-Amz-Algorithm')
38 | ) {
39 | // if the URL is already signed, then use it as-is
40 | return url
41 | }
42 | }
43 |
44 | if (u.hostname === 'img.notionusercontent.com') {
45 | return url
46 | }
47 | } catch {
48 | // ignore invalid urls
49 | }
50 |
51 | if (url.startsWith('/images')) {
52 | url = `https://www.notion.so${url}`
53 | }
54 |
55 | url = `https://www.notion.so${
56 | url.startsWith('/image') ? url : `/image/${encodeURIComponent(url)}`
57 | }`
58 |
59 | const notionImageUrlV2 = new URL(url)
60 | let table = block.parent_table === 'space' ? 'block' : block.parent_table
61 | if (table === 'collection' || table === 'team') {
62 | table = 'block'
63 | }
64 | notionImageUrlV2.searchParams.set('table', table)
65 | notionImageUrlV2.searchParams.set('id', block.id)
66 | notionImageUrlV2.searchParams.set('cache', 'v2')
67 |
68 | url = notionImageUrlV2.toString()
69 |
70 | return url
71 | }
72 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/components/mention-preview-card.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function capitalizeFirstLetter(str?: string) {
4 | if (!str) return ''
5 |
6 | return str.charAt(0).toUpperCase() + str.slice(1)
7 | }
8 |
9 | export function MentionPreviewCard({
10 | owner,
11 | lastUpdated,
12 | externalImage,
13 | title,
14 | domain
15 | }: {
16 | owner?: string
17 | lastUpdated?: string | null
18 | title: string
19 | domain: string
20 | externalImage?: React.ReactNode
21 | }) {
22 | return (
23 |
24 | {externalImage && (
25 |
26 |
{externalImage}
27 |
28 | {capitalizeFirstLetter(domain.split('.')[0])}
29 |
30 |
31 | )}
32 |
{title}
33 | {owner && (
34 |
35 |
Owner
36 |
{owner}
37 |
38 | )}
39 | {lastUpdated && (
40 |
41 |
Updated
42 |
43 | {lastUpdated}
44 |
45 |
46 | )}
47 | {domain === 'github.com' && (
48 |
49 |
53 |
57 |
58 | )}
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-status.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/react-notion-x/src/icons/type-status.tsx:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | function SvgTypeStatus(props: React.SVGProps) {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default SvgTypeStatus
12 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Suggestions and pull requests are highly encouraged. Have a look at the [open issues](https://github.com/NotionX/react-notion-x/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22+sort%3Areactions-%2B1-desc), especially [the easy ones](https://github.com/NotionX/react-notion-x/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+sort%3Areactions-%2B1-desc).
4 |
5 | ## Development
6 |
7 | To develop the project locally, you'll need a recent version of Node.js and `pnpm` installed globally.
8 |
9 | To get started, clone the repo and run `pnpm install` from the root directory:
10 |
11 | ```bash
12 | git clone https://github.com/NotionX/react-notion-x.git
13 | cd react-notion-x
14 | pnpm install
15 | ```
16 |
17 | This will install dependencies and link all of the local packages together using `lerna`. This includes the example projects which will now point to the local version of your packages.
18 |
19 | ```bash
20 | pnpm dev
21 | ```
22 |
23 | This starts compiling the packages into their respective `build` folders
24 |
25 | With `pnpm dev` running in one tab, we recommend opening a second tab and navigating to the `examples/minimal` directory.
26 |
27 | ```bash
28 | cd examples/minimal
29 | pnpm dev
30 | ```
31 |
32 | Running `pnpm dev` from the `examples/minimal` directory will start the example project's Next.js dev server. This project ill be using your locally built version of the libraries.
33 |
34 | You should now be able to open `http://localhost:3000` to view and debug the example project.
35 |
36 | ### Gotchas
37 |
38 | Whenever you make a change to one of the packages, the `pnpm dev` from the project root will re-compile that package, and the `pnpm dev` from the example project's Next.js dev server should hot-reload it in the browser.
39 |
40 | Sometimes, this process gets a little out of whack, and if you're not sure what's going on, I usually just quit one or both of the `pnpm dev` commands and restart them.
41 |
42 | If you're seeing something unexpected while debugging one of the Next.js demos, try running `rm -rf .next` to refresh the Next.js cache before running `pnpm dev` again.
43 |
--------------------------------------------------------------------------------
/examples/minimal/pages/[pageId].tsx:
--------------------------------------------------------------------------------
1 | // import { type ExtendedRecordMap } from 'notion-types'
2 |
3 | // import { NotionPage } from '../components/NotionPage'
4 | // import { rootNotionPageId, rootNotionSpaceId } from '../lib/config'
5 | // import notion from '../lib/notion'
6 |
7 | // export const getStaticProps = async (context: any) => {
8 | // const pageId = (context.params.pageId as string) || rootNotionPageId
9 | // const recordMap = await notion.getPage(pageId)
10 |
11 | // // NOTE: this isn't necessary; trying to reduce my vercel bill
12 | // const blockIds = Object.keys(recordMap.block)
13 | // const firstBlock = blockIds.length > 0 ? recordMap.block[blockIds[0]!] : null
14 | // if (rootNotionSpaceId && firstBlock?.value?.space_id !== rootNotionSpaceId) {
15 | // return {
16 | // notFound: true
17 | // }
18 | // }
19 |
20 | // return {
21 | // props: {
22 | // recordMap
23 | // },
24 | // // cache for 1 week in seconds
25 | // // NOTE: you'll likely want to use a shorter cache time for your app, but
26 | // // I'm bumping this up because my vercel bill keeps increasing due to people
27 | // // abusing the demo to host their own sites.
28 | // revalidate: 604_800
29 | // }
30 | // }
31 |
32 | // export async function getStaticPaths() {
33 | // return {
34 | // paths: [],
35 | // fallback: true
36 | // }
37 | // }
38 |
39 | // export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) {
40 | // return (
41 | //
42 | // Hey 👋 I've disabled the public demo for react-notion-x for now because my
43 | // Vercel bill keeps increasing due to people abusing the demo.
44 | //
45 | // )
46 | // }
47 |
48 | export const getStaticProps = async () => {
49 | return { props: {}, revalidate: false }
50 | }
51 |
52 | export async function getStaticPaths() {
53 | return {
54 | paths: [],
55 | fallback: true
56 | }
57 | }
58 |
59 | export default function Page() {
60 | return (
61 |
62 | Hey 👋 I've disabled the public demo for react-notion-x for now because my
63 | Vercel bill keeps increasing due to people abusing the demo.
64 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------