├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── funding.yml ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── launch.json ├── contributing.md ├── examples ├── cra │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── readme.md │ ├── src │ │ ├── App.tsx │ │ ├── config.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ └── record-map.json │ └── tsconfig.json ├── full │ ├── components │ │ ├── Loading.tsx │ │ ├── LoadingIcon.tsx │ │ ├── NotionPage.tsx │ │ └── styles.module.css │ ├── lib │ │ ├── config.ts │ │ ├── notion.ts │ │ └── preview-images.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ │ ├── [pageId].tsx │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── index.tsx │ ├── public │ │ ├── favicon.ico │ │ ├── robots.txt │ │ └── social.jpg │ ├── readme.md │ ├── styles │ │ └── globals.css │ ├── tsconfig.json │ └── turbo.json └── minimal │ ├── components │ └── NotionPage.tsx │ ├── lib │ ├── config.ts │ └── notion.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── [pageId].tsx │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx │ ├── public │ ├── favicon.ico │ └── robots.txt │ ├── readme.md │ ├── styles │ └── globals.css │ ├── tsconfig.json │ └── turbo.json ├── license ├── media ├── notion-ts.png ├── react-notion-x-perf.png └── super-so-banner.png ├── package.json ├── packages ├── notion-client │ ├── fixtures │ │ ├── blocks │ │ │ ├── audio.json │ │ │ ├── drive.json │ │ │ ├── equation.json │ │ │ ├── file.json │ │ │ └── pdf.json │ │ ├── board-results.json │ │ ├── board.json │ │ ├── collection-row-page.json │ │ ├── collection-view-page.json │ │ ├── gallery.json │ │ ├── list.json │ │ ├── page.json │ │ ├── search.json │ │ ├── signed-urls.json │ │ └── table.json │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── index.ts │ │ ├── notion-api.test.ts │ │ ├── notion-api.ts │ │ └── types.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── notion-compat │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── convert-block.ts │ │ ├── convert-color.ts │ │ ├── convert-page.ts │ │ ├── convert-rich-text.ts │ │ ├── convert-time.ts │ │ ├── index.ts │ │ ├── notion-compat-api.test.ts │ │ ├── notion-compat-api.ts │ │ └── types.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── notion-types │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── api.ts │ │ ├── block.ts │ │ ├── collection-view.ts │ │ ├── collection.ts │ │ ├── core.ts │ │ ├── formula.ts │ │ ├── index.ts │ │ ├── maps.ts │ │ └── user.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── notion-utils │ ├── fixtures │ │ └── nba │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566.json │ │ │ ├── Atlanta Hawks.json │ │ │ ├── Boston Celtics.json │ │ │ ├── Brooklyn Nets.json │ │ │ ├── Charlotte Hornets.json │ │ │ ├── Chicago Bulls.json │ │ │ ├── Cleveland Cavaliers.json │ │ │ ├── Dallas Mavericks.json │ │ │ ├── Denver Nuggest.json │ │ │ ├── Detroit Pistons.json │ │ │ ├── Duke University.json │ │ │ ├── Golden State Warriors.json │ │ │ ├── Houston Rockets.json │ │ │ ├── Indiana Pacers.json │ │ │ ├── List.json │ │ │ ├── Los Angeles Clippers.json │ │ │ ├── Los Angeles Lakers.json │ │ │ ├── Memphis Grizzlies.json │ │ │ ├── Miami Heat.json │ │ │ ├── Milwaukee Bucks.json │ │ │ ├── Minnesota Timberwolves.json │ │ │ ├── New Orleans Pelicans.json │ │ │ ├── New York Knicks.json │ │ │ ├── Oklahoma City Thunder.json │ │ │ ├── Orlando Magic.json │ │ │ ├── Philadelphia 76ers.json │ │ │ ├── Phoenix Suns.json │ │ │ ├── Portland Trail Blazers.json │ │ │ ├── Raw Table.json │ │ │ ├── Sacramento Kings.json │ │ │ ├── San Antonio Spurs.json │ │ │ ├── Toronto Raptors.json │ │ │ ├── UCLA.json │ │ │ ├── University of Kentucky.json │ │ │ ├── University of North Carolina.json │ │ │ ├── University of Texas.json │ │ │ ├── Utah Jazz.json │ │ │ ├── Washington Wizards.json │ │ │ ├── data │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-048d8535-b957-488a-89f0-ade8d3c4c026.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-1a1863bd-f819-41dc-a228-831ca7a75d2a.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-1d79cc15-d040-43eb-9d65-7e35bafbcf74.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-43d091fb-a648-40cc-ae10-9d9784c34524.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-59a7246f-e923-4faf-9246-75851294365b.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-5ae81e49-33ad-47bf-9c14-14adb9145b4d.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-693b4304-0bbd-4f9a-85f1-7c9cb1768110.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-9a43758f-21c5-4fc2-a4b5-0fc82971b79f.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-bd226193-3647-4ed1-bbe3-a5baa69954c7.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-c69e1799-88cb-4135-a10d-1ec4617f0aa6.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-cfe27a86-00d0-4322-b2db-2bc3e69269e2.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-d4102177-3cb3-4d36-8f39-92d84147f825.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-d8f02741-e03a-4e40-91d7-0b821ee4014d.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-dac0d378-1774-47fc-a3a3-103ae120a3c1.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-e1c31992-ad30-4ab5-a497-c69fc579e2ec.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-e3d28d4f-e72e-430a-92f8-263cd01ea452.json │ │ │ ├── 1a425309-21c0-4ca1-8548-c5915137b566-e8feed24-4bf6-41b5-84a7-6134f9160cc1.json │ │ │ ├── e777a528-9404-4e96-9f26-0014be705592-c01c2f48-5442-47d8-adb3-01c4f8bb8e58.json │ │ │ └── e777a528-9404-4e96-9f26-0014be705592-c0d2d91c-e2a5-4ea5-a9b7-2b2d8ae2d591.json │ │ │ └── e777a528-9404-4e96-9f26-0014be705592.json │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ ├── normalize-url.test.ts.snap │ │ │ └── parse-page-id.test.ts.snap │ │ ├── estimate-page-read-time.ts │ │ ├── format-date.ts │ │ ├── format-notion-date-time.ts │ │ ├── get-all-pages-in-space.ts │ │ ├── get-block-collection-id.ts │ │ ├── get-block-icon.ts │ │ ├── get-block-parent-page.ts │ │ ├── get-block-title.ts │ │ ├── get-canonical-page-id.ts │ │ ├── get-date-value.ts │ │ ├── get-page-breadcrumbs.ts │ │ ├── get-page-content-block-ids.ts │ │ ├── get-page-image-urls.ts │ │ ├── get-page-property.ts │ │ ├── get-page-table-of-contents.ts │ │ ├── get-page-title.ts │ │ ├── get-page-tweet-ids.ts │ │ ├── get-page-tweet-urls.ts │ │ ├── get-text-content.ts │ │ ├── id-to-uuid.ts │ │ ├── index.ts │ │ ├── is-url.ts │ │ ├── map-image-url.ts │ │ ├── map-page-url.ts │ │ ├── merge-record-maps.ts │ │ ├── normalize-title.ts │ │ ├── normalize-url.test.ts │ │ ├── normalize-url.ts │ │ ├── parse-page-id.test.ts │ │ ├── parse-page-id.ts │ │ ├── reset.d.ts │ │ └── uuid-to-id.ts │ ├── tsconfig.json │ └── tsup.config.ts └── react-notion-x │ ├── package.json │ ├── readme.md │ ├── src │ ├── block.tsx │ ├── components │ │ ├── asset-wrapper.tsx │ │ ├── asset.tsx │ │ ├── audio.tsx │ │ ├── checkbox.tsx │ │ ├── eoi.tsx │ │ ├── file.tsx │ │ ├── google-drive.tsx │ │ ├── graceful-image.tsx │ │ ├── header.tsx │ │ ├── lazy-image.tsx │ │ ├── link-mention.tsx │ │ ├── lite-youtube-embed.tsx │ │ ├── mention-preview-card.tsx │ │ ├── page-aside.tsx │ │ ├── page-icon.tsx │ │ ├── page-title.tsx │ │ ├── search-dialog.tsx │ │ ├── sync-pointer-block.tsx │ │ └── text.tsx │ ├── context.tsx │ ├── icons │ │ ├── check.svg │ │ ├── check.tsx │ │ ├── chevron-down-icon.tsx │ │ ├── clear-icon.tsx │ │ ├── collection-view-board.svg │ │ ├── collection-view-board.tsx │ │ ├── collection-view-calendar.svg │ │ ├── collection-view-calendar.tsx │ │ ├── collection-view-gallery.svg │ │ ├── collection-view-gallery.tsx │ │ ├── collection-view-icon.tsx │ │ ├── collection-view-list.svg │ │ ├── collection-view-list.tsx │ │ ├── collection-view-table.svg │ │ ├── collection-view-table.tsx │ │ ├── copy.svg │ │ ├── copy.tsx │ │ ├── default-page-icon.tsx │ │ ├── empty-icon.tsx │ │ ├── file-icon.tsx │ │ ├── link-icon.tsx │ │ ├── loading-icon.tsx │ │ ├── property-icon.tsx │ │ ├── search-icon.tsx │ │ ├── type-auto-increment-id.svg │ │ ├── type-auto-increment-id.tsx │ │ ├── type-checkbox.svg │ │ ├── type-checkbox.tsx │ │ ├── type-date.svg │ │ ├── type-date.tsx │ │ ├── type-email.svg │ │ ├── type-email.tsx │ │ ├── type-file.svg │ │ ├── type-file.tsx │ │ ├── type-formula.svg │ │ ├── type-formula.tsx │ │ ├── type-github.tsx │ │ ├── type-multi-select.svg │ │ ├── type-multi-select.tsx │ │ ├── type-number.svg │ │ ├── type-number.tsx │ │ ├── type-person-2.svg │ │ ├── type-person-2.tsx │ │ ├── type-person.svg │ │ ├── type-person.tsx │ │ ├── type-phone-number.svg │ │ ├── type-phone-number.tsx │ │ ├── type-relation.svg │ │ ├── type-relation.tsx │ │ ├── type-select.svg │ │ ├── type-select.tsx │ │ ├── type-status.svg │ │ ├── type-status.tsx │ │ ├── type-text.svg │ │ ├── type-text.tsx │ │ ├── type-timestamp.svg │ │ ├── type-timestamp.tsx │ │ ├── type-title.svg │ │ ├── type-title.tsx │ │ ├── type-url.svg │ │ └── type-url.tsx │ ├── index.ts │ ├── next.tsx │ ├── renderer.tsx │ ├── styles.css │ ├── third-party │ │ ├── code.tsx │ │ ├── collection-card.tsx │ │ ├── collection-column-title.tsx │ │ ├── collection-group.tsx │ │ ├── collection-row.tsx │ │ ├── collection-utils.ts │ │ ├── collection-view-board.tsx │ │ ├── collection-view-gallery.tsx │ │ ├── collection-view-list.tsx │ │ ├── collection-view-table.tsx │ │ ├── collection-view.tsx │ │ ├── collection.tsx │ │ ├── collections.md │ │ ├── equation.tsx │ │ ├── eval-formula.ts │ │ ├── modal.tsx │ │ ├── pdf.tsx │ │ ├── property.tsx │ │ ├── react-use.ts │ │ └── readme.md │ ├── types.ts │ └── utils.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── readme.md ├── tsconfig.json └── turbo.json /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .snapshots/ 2 | .next/ 3 | .vercel/ 4 | build/ 5 | docs/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["@fisch0920/eslint-config"], 4 | "rules": { 5 | "react/prop-types": "off", 6 | "unicorn/no-array-reduce": "off", 7 | "unicorn/filename-case": "off", 8 | "no-process-env": "off", 9 | "array-callback-return": "off", 10 | "jsx-a11y/click-events-have-key-events": "off", 11 | "jsx-a11y/no-static-element-interactions": "off", 12 | "jsx-a11y/media-has-caption": "off", 13 | "jsx-a11y/interactive-supports-focus": "off", 14 | "@typescript-eslint/naming-convention": "off" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: [transitive-bullshit] 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | - 18 15 | - 22 16 | - 23 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: pnpm/action-setup@v4 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'pnpm' 25 | 26 | - run: pnpm install --frozen-lockfile --strict-peer-dependencies 27 | - run: pnpm build 28 | - run: pnpm test 29 | 30 | - name: Build minimal example 31 | run: | 32 | cd examples/minimal 33 | pnpm build 34 | 35 | - name: Build full example 36 | run: | 37 | cd examples/full 38 | pnpm build 39 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts=true 2 | package-manager-strict=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .snapshots/ 2 | .next/ 3 | .vercel/ 4 | build/ 5 | docs/ 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": false, 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "bracketSpacing": true, 8 | "bracketSameLine": false, 9 | "arrowParens": "always", 10 | "trailingComma": "none" 11 | } 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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` 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 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/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 | -------------------------------------------------------------------------------- /examples/cra/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-x-example-cra", 3 | "version": "7.3.0", 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": "^22.13.10", 13 | "@types/react": "^19.0.11", 14 | "@types/react-dom": "^19.0.4", 15 | "notion-types": "workspace:*", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0", 18 | "react-notion-x": "workspace:*", 19 | "react-scripts": "5.0.1" 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 | -------------------------------------------------------------------------------- /examples/cra/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/examples/cra/public/favicon.ico -------------------------------------------------------------------------------- /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 | 35 |
36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/cra/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/examples/cra/public/logo192.png -------------------------------------------------------------------------------- /examples/cra/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/examples/cra/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/cra/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/cra/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/cra/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | false || 18 | (process.env.USE_OFFICIAL_NOTION_API === 'true' && process.env.NOTION_TOKEN) 19 | 20 | export const isDev = 21 | process.env.NODE_ENV === 'development' || !process.env.NODE_ENV 22 | 23 | export const port = process.env.PORT || 3000 24 | export const rootDomain = isDev ? `localhost:${port}` : null 25 | -------------------------------------------------------------------------------- /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) 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/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) { 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 | -------------------------------------------------------------------------------- /examples/full/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /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/full/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-x-example-full", 3 | "version": "7.3.0", 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": "^2.3.0", 16 | "classnames": "^2.5.1", 17 | "ky": "^1.7.5", 18 | "lqip-modern": "^2.1.0", 19 | "next": "^15.2.3", 20 | "notion-client": "workspace:*", 21 | "notion-compat": "workspace:*", 22 | "notion-types": "workspace:*", 23 | "notion-utils": "workspace:*", 24 | "p-map": "^7.0.3", 25 | "p-memoize": "^7.1.1", 26 | "prismjs": "^1.30.0", 27 | "react": "^19.0.0", 28 | "react-dom": "^19.0.0", 29 | "react-notion-x": "workspace:*", 30 | "react-tweet-embed": "^2.0.0" 31 | }, 32 | "devDependencies": { 33 | "cross-env": "^7.0.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/full/pages/[pageId].tsx: -------------------------------------------------------------------------------- 1 | import { type ExtendedRecordMap } from 'notion-types' 2 | import { defaultMapPageUrl, getAllPagesInSpace } from 'notion-utils' 3 | 4 | import { NotionPage } from '../components/NotionPage' 5 | import { 6 | isDev, 7 | previewImagesEnabled, 8 | rootDomain, 9 | rootNotionPageId, 10 | rootNotionSpaceId 11 | } from '../lib/config' 12 | import * as notion from '../lib/notion' 13 | 14 | export const getStaticProps = async (context) => { 15 | const pageId = context.params.pageId as string 16 | const recordMap = await notion.getPage(pageId) 17 | 18 | return { 19 | props: { 20 | recordMap 21 | }, 22 | revalidate: 10 23 | } 24 | } 25 | 26 | export async function getStaticPaths() { 27 | if (isDev) { 28 | return { 29 | paths: [], 30 | fallback: true 31 | } 32 | } 33 | 34 | const mapPageUrl = defaultMapPageUrl(rootNotionPageId) 35 | 36 | // This crawls all public pages starting from the given root page in order 37 | // for next.js to pre-generate all pages via static site generation (SSG). 38 | // This is a useful optimization but not necessary; you could just as easily 39 | // set paths to an empty array to not pre-generate any pages at build time. 40 | const pages = await getAllPagesInSpace( 41 | rootNotionPageId, 42 | rootNotionSpaceId, 43 | notion.getPage, 44 | { 45 | traverseCollections: false 46 | } 47 | ) 48 | 49 | const paths = Object.keys(pages) 50 | .map((pageId) => mapPageUrl(pageId)) 51 | .filter((path) => path && path !== '/') 52 | 53 | return { 54 | paths, 55 | fallback: true 56 | } 57 | } 58 | 59 | export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) { 60 | return ( 61 | 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /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 }) { 11 | return 12 | } 13 | 14 | export default MyApp 15 | -------------------------------------------------------------------------------- /examples/full/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document' 2 | 3 | export default class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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: 10 20 | } 21 | } 22 | 23 | export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) { 24 | return ( 25 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /examples/full/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/examples/full/public/favicon.ico -------------------------------------------------------------------------------- /examples/full/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Disallow: /api/* -------------------------------------------------------------------------------- /examples/full/public/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/examples/full/public/social.jpg -------------------------------------------------------------------------------- /examples/full/styles/globals.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | } 9 | -------------------------------------------------------------------------------- /examples/full/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "exclude": ["node_modules"], 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/full/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": [".next/**", "!.next/cache/**"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | export const isDev = 5 | process.env.NODE_ENV === 'development' || !process.env.NODE_ENV 6 | -------------------------------------------------------------------------------- /examples/minimal/lib/notion.ts: -------------------------------------------------------------------------------- 1 | import { NotionAPI } from 'notion-client' 2 | 3 | const notion = new NotionAPI() 4 | export default notion 5 | -------------------------------------------------------------------------------- /examples/minimal/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/minimal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-x-example-minimal", 3 | "version": "7.3.0", 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": "^15.2.3", 16 | "notion-client": "workspace:*", 17 | "notion-types": "workspace:*", 18 | "notion-utils": "workspace:*", 19 | "react": "^19.0.0", 20 | "react-dom": "^19.0.0", 21 | "react-notion-x": "workspace:*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/minimal/pages/[pageId].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 (context) => { 8 | const pageId = (context.params.pageId as string) || rootNotionPageId 9 | const recordMap = await notion.getPage(pageId) 10 | 11 | return { 12 | props: { 13 | recordMap 14 | }, 15 | revalidate: 10 16 | } 17 | } 18 | 19 | export async function getStaticPaths() { 20 | return { 21 | paths: [], 22 | fallback: true 23 | } 24 | } 25 | 26 | export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) { 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /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 }) { 12 | return 13 | } 14 | 15 | export default MyApp 16 | -------------------------------------------------------------------------------- /examples/minimal/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document' 2 | 3 | export default class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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 | revalidate: 10 16 | } 17 | } 18 | 19 | export default function Page({ recordMap }: { recordMap: ExtendedRecordMap }) { 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /examples/minimal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/examples/minimal/public/favicon.ico -------------------------------------------------------------------------------- /examples/minimal/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: * -------------------------------------------------------------------------------- /examples/minimal/readme.md: -------------------------------------------------------------------------------- 1 |

2 | React Notion X 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 twitter 32 | -------------------------------------------------------------------------------- /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/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "exclude": ["node_modules"], 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/minimal/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": [".next/**", "!.next/cache/**"] 6 | } 7 | } 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 | -------------------------------------------------------------------------------- /media/notion-ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/media/notion-ts.png -------------------------------------------------------------------------------- /media/react-notion-x-perf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/media/react-notion-x-perf.png -------------------------------------------------------------------------------- /media/super-so-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotionX/react-notion-x/f8cdd7e32c3a97fc93d157b379d738f3a4aac5cd/media/super-so-banner.png -------------------------------------------------------------------------------- /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.3.0", 9 | "packageManager": "pnpm@10.6.4", 10 | "engines": { 11 | "node": ">=18" 12 | }, 13 | "scripts": { 14 | "build": "turbo build --filter='./packages/*'", 15 | "dev": "turbo dev --concurrency 50 --continue", 16 | "clean": "turbo clean", 17 | "test": "turbo test", 18 | "test:format": "prettier --check \"**/*.{js,ts,tsx}\"", 19 | "test:lint": "turbo test:lint", 20 | "test:typecheck": "turbo test:typecheck", 21 | "test:unit": "turbo test:unit", 22 | "release": "bumpp -r && pnpm publish -r", 23 | "pretest": "run-s build", 24 | "preinstall": "npx only-allow pnpm", 25 | "prepare": "simple-git-hooks" 26 | }, 27 | "devDependencies": { 28 | "@fisch0920/eslint-config": "^1.4.0", 29 | "@total-typescript/ts-reset": "^0.6.1", 30 | "@types/node": "^22.13.10", 31 | "bumpp": "^10.1.0", 32 | "del-cli": "^6.0.0", 33 | "eslint": "^8.57.1", 34 | "npm-run-all2": "^7.0.2", 35 | "prettier": "^3.5.3", 36 | "react": "^19.0.0", 37 | "react-dom": "^19.0.0", 38 | "simple-git-hooks": "^2.11.1", 39 | "tsup": "^8.4.0", 40 | "tsx": "^4.19.3", 41 | "turbo": "^2.4.4", 42 | "typescript": "^5.8.2", 43 | "vitest": "^3.0.9" 44 | }, 45 | "simple-git-hooks": { 46 | "pre-commit": "npx lint-staged" 47 | }, 48 | "lint-staged": { 49 | "*.{ts,tsx}": [ 50 | "prettier --ignore-unknown --write", 51 | "eslint --fix" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /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-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-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/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-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-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-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 | -------------------------------------------------------------------------------- /packages/notion-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-client", 3 | "version": "7.3.0", 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 | "ky": "^1.7.5", 31 | "notion-types": "workspace:*", 32 | "notion-utils": "workspace:*", 33 | "p-map": "^7.0.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/notion-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notion-api' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /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 | '067dd719-a912-471e-a9a3-ac10710e7fdf', 7 | '067dd719a912471ea9a3ac10710e7fdf', 8 | 'https://www.notion.so/saasifysh/Embeds-5d4e290ca4604d8fb809af806a6c1749', 9 | 'https://www.notion.so/saasifysh/File-Uploads-34d650c65da34f888335dbd3ddd141dc', 10 | 'Color-Rainbow-54bf56611797480c951e5c1f96cb06f2', 11 | 'e68c18a461904eb5a2ddc3748e76b893', 12 | 'https://www.notion.so/saasifysh/Saasify-Key-Takeaways-689a8abc1afa4699905aa2f2e585e208', 13 | 'https://www.notion.so/saasifysh/TransitiveBullsh-it-78fc5a4b88d74b0e824e29407e9f1ec1', 14 | 'https://www.notion.so/saasifysh/About-8d0062776d0c4afca96eb1ace93a7538', 15 | 'https://www.notion.so/potionsite/newest-board-a899b98b7cdc424585e5ddebbdae60cc' 16 | 17 | // collections stress test 18 | // NOTE: removing because of sporadic timeouts 19 | // 'nba-3f92ae505636427c897634a15b9f2892' 20 | ] 21 | 22 | const pageIdFixturesFailure = [ 23 | 'bdecdf150d0e40cb9f3412be132335d4', // private page 24 | 'foo' // invalid page id 25 | ] 26 | 27 | for (const pageId of pageIdFixturesSuccess) { 28 | test( 29 | `NotionAPI.getPage success ${pageId}`, 30 | { 31 | timeout: 60_000 // one minute timeout 32 | }, 33 | async () => { 34 | const api = new NotionAPI() 35 | const page = await api.getPage(pageId) 36 | 37 | expect(page).toBeTruthy() 38 | expect(page.block).toBeTruthy() 39 | } 40 | ) 41 | } 42 | 43 | for (const pageId of pageIdFixturesFailure) { 44 | test(`NotionAPI.getPage failure ${pageId}`, async () => { 45 | const api = new NotionAPI() 46 | await expect(() => api.getPage(pageId)).rejects.toThrow() 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /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/notion-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "build" 6 | }, 7 | "include": ["src"] 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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-compat", 3 | "version": "7.3.0", 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": "^8.1.0" 33 | }, 34 | "devDependencies": { 35 | "@notionhq/client": "^2.3.0", 36 | "notion-client": "workspace:*" 37 | }, 38 | "peerDependencies": { 39 | "@notionhq/client": "^2.2.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/notion-compat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "build" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-types", 3 | "version": "7.3.0", 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-types/readme.md: -------------------------------------------------------------------------------- 1 |

2 | React Notion X 3 |

4 | 5 | # notion-types 6 | 7 | > TypeScript types for core Notion data structures. 8 | 9 | [![NPM](https://img.shields.io/npm/v/notion-types.svg)](https://www.npmjs.com/package/notion-types) [![Build Status](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml/badge.svg)](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](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 twitter 34 | -------------------------------------------------------------------------------- /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-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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/notion-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "build" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /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/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-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-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/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/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-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/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-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-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-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-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-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 | } -------------------------------------------------------------------------------- /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-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/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-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-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/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-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/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-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/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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-utils", 3 | "version": "7.3.0", 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": "^6.1.0", 37 | "memoize": "^10.1.0", 38 | "normalize-url": "^8.0.1", 39 | "notion-types": "workspace:*", 40 | "p-queue": "^8.1.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/notion-utils/readme.md: -------------------------------------------------------------------------------- 1 |

2 | React Notion X 3 |

4 | 5 | # notion-utils 6 | 7 | > Useful utilities for working with Notion data. Isomorphic. 8 | 9 | [![NPM](https://img.shields.io/npm/v/notion-utils.svg)](https://www.npmjs.com/package/notion-utils) [![Build Status](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml/badge.svg)](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](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 twitter 45 | -------------------------------------------------------------------------------- /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/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-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/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/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/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 | -------------------------------------------------------------------------------- /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-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/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 | -------------------------------------------------------------------------------- /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 | 46 | // eslint-disable-next-line no-constant-condition 47 | } while (true) 48 | 49 | breadcrumbs.reverse() 50 | 51 | return breadcrumbs 52 | } 53 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/notion-utils/src/is-url.ts: -------------------------------------------------------------------------------- 1 | export { default as isUrl } from 'is-url-superb' 2 | -------------------------------------------------------------------------------- /packages/notion-utils/src/map-image-url.ts: -------------------------------------------------------------------------------- 1 | import { type Block } from 'notion-types' 2 | 3 | export const defaultMapImageUrl = ( 4 | url: string | undefined, 5 | block: Block 6 | ): string | undefined => { 7 | if (!url) { 8 | return undefined 9 | } 10 | 11 | if (url.startsWith('data:')) { 12 | return url 13 | } 14 | 15 | // more recent versions of notion don't proxy unsplash images 16 | if (url.startsWith('https://images.unsplash.com')) { 17 | return url 18 | } 19 | 20 | try { 21 | const u = new URL(url) 22 | 23 | if ( 24 | u.pathname.startsWith('/secure.notion-static.com') && 25 | u.hostname.endsWith('.amazonaws.com') 26 | ) { 27 | if ( 28 | u.searchParams.has('X-Amz-Credential') && 29 | u.searchParams.has('X-Amz-Signature') && 30 | u.searchParams.has('X-Amz-Algorithm') 31 | ) { 32 | // if the URL is already signed, then use it as-is 33 | return url 34 | } 35 | } 36 | } catch { 37 | // ignore invalid urls 38 | } 39 | 40 | if (url.startsWith('/images')) { 41 | url = `https://www.notion.so${url}` 42 | } 43 | 44 | url = `https://www.notion.so${ 45 | url.startsWith('/image') ? url : `/image/${encodeURIComponent(url)}` 46 | }` 47 | 48 | const notionImageUrlV2 = new URL(url) 49 | let table = block.parent_table === 'space' ? 'block' : block.parent_table 50 | if (table === 'collection' || table === 'team') { 51 | table = 'block' 52 | } 53 | notionImageUrlV2.searchParams.set('table', table) 54 | notionImageUrlV2.searchParams.set('id', block.id) 55 | notionImageUrlV2.searchParams.set('cache', 'v2') 56 | 57 | url = notionImageUrlV2.toString() 58 | 59 | return url 60 | } 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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-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 | null = '', 16 | { uuid = true }: { uuid?: boolean } = {} 17 | ) => { 18 | if (!id) return null 19 | 20 | id = id.split('?')[0]! 21 | if (!id) return null 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 null 35 | } 36 | -------------------------------------------------------------------------------- /packages/notion-utils/src/reset.d.ts: -------------------------------------------------------------------------------- 1 | import '@total-typescript/ts-reset' 2 | -------------------------------------------------------------------------------- /packages/notion-utils/src/uuid-to-id.ts: -------------------------------------------------------------------------------- 1 | export const uuidToId = (uuid: string) => uuid.replaceAll('-', '') 2 | -------------------------------------------------------------------------------- /packages/notion-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "build" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /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/readme.md: -------------------------------------------------------------------------------- 1 |

2 | React Notion X 3 |

4 | 5 | # React Notion X 6 | 7 | > Fast and accurate React renderer for Notion. 8 | 9 | [![NPM](https://img.shields.io/npm/v/react-notion-x.svg)](https://www.npmjs.com/package/react-notion-x) [![Build Status](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml/badge.svg)](https://github.com/NotionX/react-notion-x/actions/workflows/test.yml) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](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 twitter 20 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/components/audio.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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 |
31 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/components/file.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/components/graceful-image.tsx: -------------------------------------------------------------------------------- 1 | import * as 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/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/components/page-title.tsx: -------------------------------------------------------------------------------- 1 | import { type Block, type Decoration } from 'notion-types' 2 | import { getBlockTitle } from 'notion-utils' 3 | import * as 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/react-notion-x/src/components/sync-pointer-block.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/check.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/chevron-down-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export function ChevronDownIcon(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 * as 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/collection-view-board.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-board.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgCollectionViewBoard(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgCollectionViewBoard 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-calendar.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgCollectionViewCalendar(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgCollectionViewCalendar 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-gallery.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-gallery.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/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/collection-view-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-list.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgCollectionViewList(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgCollectionViewList 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-table.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/collection-view-table.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/src/icons/copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/copy.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgCopy(props: React.SVGProps) { 4 | return ( 5 | 12 | 16 | 20 | 21 | ) 22 | } 23 | 24 | export default SvgCopy 25 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/default-page-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export function DefaultPageIcon(props: any) { 4 | const { className, ...rest } = props 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/empty-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export function EmptyIcon(props: any) { 4 | const { className, ...rest } = props 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/file-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as 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/link-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export function LinkIcon(props: any) { 4 | const { className, ...rest } = props 5 | return ( 6 | 13 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/loading-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/search-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as 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 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-auto-increment-id.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-auto-increment-id.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeAutoIncrementId(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeAutoIncrementId 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-checkbox.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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-date.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-date.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/icons/type-email.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-file.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeFile(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeFile 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-formula.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-formula.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeFormula(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeFormula 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-multi-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-multi-select.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/type-number.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-number.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/type-person-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-person-2.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypePerson2(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypePerson2 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-person.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-person.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/react-notion-x/src/icons/type-phone-number.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypePhoneNumber(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypePhoneNumber 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-relation.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-relation.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeRelation(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeRelation 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-select.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeSelect(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeSelect 12 | -------------------------------------------------------------------------------- /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 * as React from 'react' 2 | 3 | function SvgTypeStatus(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeStatus 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-text.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeText(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeText 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-timestamp.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-timestamp.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/type-title.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | function SvgTypeTitle(props: React.SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | 11 | export default SvgTypeTitle 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-url.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/icons/type-url.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/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 | -------------------------------------------------------------------------------- /packages/react-notion-x/src/third-party/collection-column-title.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/third-party/collection-group.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/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, YYY 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/third-party/collection-view.tsx: -------------------------------------------------------------------------------- 1 | import * as 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 | -------------------------------------------------------------------------------- /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/react-notion-x/src/third-party/equation.tsx: -------------------------------------------------------------------------------- 1 | import type * as 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/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/src/third-party/pdf.tsx: -------------------------------------------------------------------------------- 1 | import * as 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/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 twitter 12 | -------------------------------------------------------------------------------- /packages/react-notion-x/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "build" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /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'] 22 | }) 23 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | 5 | onlyBuiltDependencies: 6 | - simple-git-hooks 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["esnext", "ESNext.Array", "dom", "dom.iterable"], 4 | "module": "esnext", 5 | "moduleResolution": "bundler", 6 | "moduleDetection": "force", 7 | "noEmit": true, 8 | "target": "es2020", 9 | "outDir": "build", 10 | 11 | "allowImportingTsExtensions": false, 12 | "allowJs": true, 13 | "esModuleInterop": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "incremental": false, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "noUncheckedIndexedAccess": true, 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true, 21 | "sourceMap": true, 22 | "strict": true, 23 | "useDefineForClassFields": true, 24 | "verbatimModuleSyntax": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------