diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 18e7728a37..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** - - -**To Reproduce** - - -**Misc** - -- Node version: -- Package manager: -- Browser: -- [ ] I'm a [sponsor](https://www.blocknotejs.org/pricing) and would appreciate if you could look into this sooner than later 💖 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..372e429b34 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,101 @@ +name: Bug report +description: Report a bug or broken behavior in BlockNote +labels: + - bug + - needs-triage +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug! + Please use this template to describe **broken or incorrect behavior**. + + Feature ideas or questions should go to **Discussions**. + + - type: textarea + id: problem + attributes: + label: What’s broken? + description: > + Describe the problem clearly and concisely. + What is happening that should not be happening? + placeholder: > + Example: + When editing a table cell and pressing Enter, the editor crashes and the document cannot be recovered. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect to happen? + description: > + Describe the expected or correct behavior. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: > + Provide clear steps so we can reproduce the issue. + If possible, an online reproduction (e.g. StackBlitz) is extremely helpful. + placeholder: | + 1. Create a new document + 2. Insert a table + 3. Click inside a cell + 4. Press Enter + + Optional: If you can, provide a minimal online reproduction. + You can use this starter sandbox: + https://stackblitz.com/github/TypeCellOS/BlockNote/tree/main/examples/01-basic/01-minimal?file=App.tsx + validations: + required: false + + - type: input + id: version + attributes: + label: BlockNote version + description: > + Optional — specify the version you’re using, if known. + placeholder: e.g. v0.18.2 + validations: + required: false + + - type: input + id: environment + attributes: + label: Environment + description: > + Browser, OS, framework, or other relevant environment details. + placeholder: e.g. Chrome 121, macOS 14, React 18 + validations: + required: false + + - type: textarea + id: additional + attributes: + label: Additional context + description: > + Screenshots, videos, logs, or any other context that might help. + validations: + required: false + + - type: checkboxes + id: contribute + attributes: + label: Contribution + options: + - label: "I'd be interested in contributing a fix for this issue" + required: false + + - type: checkboxes + id: sponsor + attributes: + label: Sponsor + description: > + Optional — helps us prioritize first response according to our SLA. + options: + - label: "I'm a [sponsor](https://www.blocknotejs.org/pricing) and would appreciate if you could look into this sooner than later 💖" + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..f258f05126 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false + +contact_links: + - name: Share an idea or suggest an enhancement + url: https://github.com/TypeCellOS/BlockNote/discussions/categories/ideas-enhancements + about: Share feature ideas, enhancement suggestions, or other ideas for the BlockNote project. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index e8dc8c2e5b..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. - -**Bonus** -[ ] I'm a [sponsor](https://www.blocknotejs.org/pricing) and would appreciate if you could look into this sooner than later 💖 diff --git a/.github/ISSUE_TEMPLATE/share_block.yml b/.github/ISSUE_TEMPLATE/share_block.yml deleted file mode 100644 index 889bb404be..0000000000 --- a/.github/ISSUE_TEMPLATE/share_block.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: "Submit a Custom Block" -description: "Use this template to submit a new custom block (schema) for BlockNote." -title: "" -body: - - type: markdown - attributes: - value: | - ## Submit a Custom Block (Schema) - - Thank you for contributing to BlockNote! Please fill out the following sections to submit your custom block. Make sure to provide as much detail as possible to help us understand and showcase your block effectively. - - - type: input - id: block_name - attributes: - label: "Block Name" - description: "Enter the name of your custom block." - placeholder: "e.g., Alert Block" - - - type: textarea - id: block_description - attributes: - label: "Block Description" - description: "Provide a detailed description of your custom block. Explain its purpose and how it works." - placeholder: "This block is used to display alert messages with different severity levels." - - - type: textarea - id: block_dependencies - attributes: - label: "Dependencies" - description: "Provide a full list of all the dependencies needed to make your custom block work." - placeholder: "`npm_package_1`, `npm_package_2`, ...etc " - - - type: input - id: live_example - attributes: - label: "Live Example" - description: "Please provide a valid URL to a codepen, stackblitz, codeSandbox, or simply a github repo" - placeholder: "live example URL here" - - - type: textarea - id: usage_instructions - attributes: - label: "Usage Instructions" - description: "Explain how to use your custom block in a BlockNote application. Include any necessary setup steps." - placeholder: | - ```markdown - 1. Import your custom block. - 2. Add it to the BlockNote editor configuration. - 3. Use it within the BlockNote editor. - ``` - - - type: textarea - id: additional_notes - attributes: - label: "Additional Notes" - description: "Include any additional information or notes about your custom block." - placeholder: "Any additional context or information." diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1b78160209..165dad4e01 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,14 +21,13 @@ updates: - dependency-name: "@tiptap/extension-code" - dependency-name: "@tiptap/extension-horizontal-rule" - dependency-name: "@tiptap/extension-italic" - - dependency-name: "@tiptap/extension-link" + - dependency-name: "@tiptap/extension-paragraph" - dependency-name: "@tiptap/extension-strike" - dependency-name: "@tiptap/extension-text" - dependency-name: "@tiptap/extension-underline" # prosemirror packages - dependency-name: "prosemirror-changeset" - - dependency-name: "prosemirror-dropcursor" - dependency-name: "prosemirror-highlight" - dependency-name: "prosemirror-model" - dependency-name: "prosemirror-state" @@ -41,6 +40,12 @@ updates: # yjs packages - dependency-name: "yjs" - dependency-name: "y-prosemirror" + ignore: + # Hono packages are used only in the demo AI server and are not part of + # the main editor/runtime surface area. + - dependency-name: "hono" + - dependency-name: "@hono/node-server" + - dependency-name: "@hono/*" groups: editor-dependencies: patterns: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa013cc9a2..91b5ca0414 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,10 @@ on: types: [opened, synchronize, reopened, edited] env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }} NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_SERVER }} + pnpm_config_store_dir: ./node_modules/.pnpm-store jobs: build: @@ -16,23 +18,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 100 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - - uses: nrwl/nx-set-shas@v3 + - uses: nrwl/nx-set-shas@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" node-version-file: ".nvmrc" - name: Cache NX - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .nx/cache key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} @@ -41,8 +43,6 @@ jobs: nx-${{ env.NX_BRANCH }}- nx- - # This is needed for the canvas dep, Tiptap V3 should remove the need for this - - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - name: Install Dependencies run: pnpm install @@ -55,6 +55,9 @@ jobs: - name: Run unit tests run: pnpm run test + - name: Run Next.js integration test (production build) + run: NEXTJS_TEST_MODE=build npx vitest run tests/src/unit/nextjs/serverUtil.test.ts + - name: Upload webpack stats artifact (editor) uses: relative-ci/agent-upload-artifact-action@v2 with: @@ -65,57 +68,140 @@ jobs: id: soft-release run: pnpx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact - playwright: - name: "Playwright Tests - ${{ matrix.browser }}" + playwright-build: + name: "Playwright Build" runs-on: ubuntu-latest - timeout-minutes: 60 - container: - image: mcr.microsoft.com/playwright:v1.51.1-noble - strategy: - fail-fast: false - matrix: - browser: [chromium, firefox, webkit] + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 100 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - - uses: nrwl/nx-set-shas@v3 + - uses: nrwl/nx-set-shas@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" node-version-file: ".nvmrc" - name: Cache NX - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .nx/cache - key: nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} + key: nx-playwright-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} restore-keys: | - nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- - nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}- + nx-playwright-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- + nx-playwright-${{ env.NX_BRANCH }}- nx- - - run: apt-get update && apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - - name: Install dependencies run: pnpm install - name: Build packages run: pnpm run build + - name: Upload build artifacts + uses: actions/upload-artifact@v7 + with: + name: playwright-build + path: | + packages/*/dist + playground/dist + retention-days: 1 + + playwright: + name: "Playwright Tests - ${{ matrix.browser }} (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})" + runs-on: ubuntu-latest + needs: playwright-build + timeout-minutes: 30 + container: + image: mcr.microsoft.com/playwright:v1.51.1-noble + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] + shardIndex: [1, 2] + shardTotal: [2] + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 100 + + - name: Install pnpm + uses: pnpm/action-setup@v5 + + - uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: "**/pnpm-lock.yaml" + node-version-file: ".nvmrc" + + - name: Download build artifacts + uses: actions/download-artifact@v8 + with: + name: playwright-build + + - name: Install dependencies + run: pnpm install + - name: Run server and Playwright tests run: | - HOME=/root PLAYWRIGHT_CONFIG="--project ${{ matrix.browser }}" pnpm run e2e + HOME=/root PLAYWRIGHT_CONFIG="--project ${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}" pnpm run e2e + + - name: Upload blob report + uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/blob-report/ + retention-days: 1 + + - name: Upload HTML report + uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: playwright-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/playwright-report/ + retention-days: 30 + + merge-reports: + name: "Merge Playwright Reports" + if: ${{ !cancelled() }} + needs: playwright + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v5 + + - uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: "**/pnpm-lock.yaml" + node-version-file: ".nvmrc" + + - name: Install dependencies + run: pnpm install + + - name: Download blob reports + uses: actions/download-artifact@v8 + with: + path: tests/all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge reports + run: npx playwright merge-reports --reporter html ./all-blob-reports + working-directory: tests - - uses: actions/upload-artifact@v4 - if: always() + - name: Upload merged HTML report + uses: actions/upload-artifact@v7 with: - name: playwright-report-${{ matrix.browser }} + name: playwright-report-merged path: tests/playwright-report/ retention-days: 30 diff --git a/.github/workflows/fresh-install-tests.yml b/.github/workflows/fresh-install-tests.yml new file mode 100644 index 0000000000..6d6ed4a452 --- /dev/null +++ b/.github/workflows/fresh-install-tests.yml @@ -0,0 +1,144 @@ +name: Fresh Install Tests + +# Periodically tests BlockNote with the latest versions of its production +# dependencies (within declared semver ranges). This catches breakage when a +# new release of a dep like @tiptap/* or prosemirror-* ships and conflicts +# with BlockNote's declared ranges — the kind of failure a user would hit when +# running `npm install @blocknote/react` in a fresh project. +# +# Only production dependencies of published (non-private) packages are updated. +# DevDependencies (vitest, vite, typescript, etc.) stay pinned to the lockfile, +# so test tooling churn doesn't cause false positives. + +on: + schedule: + - cron: "0 2 * * *" # Daily at 02:00 UTC + workflow_dispatch: # Allow manual runs + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + pnpm_config_store_dir: ./node_modules/.pnpm-store + +jobs: + fresh-install-unit-tests: + name: Unit Tests (Fresh Dep Resolution) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - id: checkout + uses: actions/checkout@v6 + + - id: install_pnpm + name: Install pnpm + uses: pnpm/action-setup@v5 + + - id: setup_node + uses: actions/setup-node@v6 + with: + node-version-file: ".nvmrc" + # Intentionally no pnpm cache — we want fresh prod dep resolution + + - id: install_dependencies + name: Install dependencies + run: pnpm install + + - id: update_prod_deps + name: Update prod deps of published packages + # Resolves production dependencies of every published (non-private) + # workspace package to the latest version within their declared semver + # ranges. This simulates what a user gets when running + # `npm install @blocknote/react` in a fresh project. + # DevDependencies are left at their lockfile versions. + run: | + FILTERS=$(node -e " + const fs = require('fs'); + const path = require('path'); + fs.readdirSync('packages').forEach(dir => { + try { + const pkg = JSON.parse(fs.readFileSync(path.join('packages', dir, 'package.json'), 'utf8')); + if (!pkg.private && pkg.name) process.stdout.write('--filter ' + pkg.name + ' '); + } catch {} + }); + ") + echo "Updating prod deps for: $FILTERS" + eval pnpm update --prod $FILTERS + + - id: dedupe_deps + name: Dedupe transitive dependencies + # After bumping the publishable packages' prod deps, collapse any + # duplicate transitive resolutions (e.g. @tiptap/core + @tiptap/pm) + # that would otherwise differ between the updated publishable packages + # and the un-updated examples/playground. Without this, TypeScript + # treats the two copies' exports as unrelated types and example-editor + # fails to build (TS2322 on Extension vs AnyExtension). + # Dedupe only rewrites the lockfile — it does NOT modify package.json, + # so the examples' "@blocknote/*": "latest" specs (which is what + # CodeSandbox users see) stay intact. + run: pnpm dedupe + + - id: build_packages + name: Build packages + run: pnpm run build + env: + NX_SKIP_NX_CACHE: "true" + + - id: run_unit_tests + name: Run unit tests + run: pnpm run test + env: + NX_SKIP_NX_CACHE: "true" + + - name: Notify Slack on workflow failure + if: ${{ failure() }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + REPOSITORY: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + RUN_ID: ${{ github.run_id }} + RUN_NUMBER: ${{ github.run_number }} + RUN_ATTEMPT: ${{ github.run_attempt }} + BRANCH: ${{ github.ref_name }} + run: | + if [ -z "$SLACK_WEBHOOK_URL" ]; then + echo "SLACK_WEBHOOK_URL is not configured; skipping Slack notification." + exit 0 + fi + + failed_step="Unknown step" + if [ "${{ steps.checkout.outcome }}" = "failure" ]; then + failed_step="Checkout repository" + elif [ "${{ steps.install_pnpm.outcome }}" = "failure" ]; then + failed_step="Install pnpm" + elif [ "${{ steps.setup_node.outcome }}" = "failure" ]; then + failed_step="Setup Node.js" + elif [ "${{ steps.install_dependencies.outcome }}" = "failure" ]; then + failed_step="Install dependencies" + elif [ "${{ steps.update_prod_deps.outcome }}" = "failure" ]; then + failed_step="Update prod deps of published packages" + elif [ "${{ steps.dedupe_deps.outcome }}" = "failure" ]; then + failed_step="Dedupe transitive dependencies" + elif [ "${{ steps.build_packages.outcome }}" = "failure" ]; then + failed_step="Build packages" + elif [ "${{ steps.run_unit_tests.outcome }}" = "failure" ]; then + failed_step="Run unit tests" + fi + + run_url="https://github.com/${REPOSITORY}/actions/runs/${RUN_ID}" + message=$(printf '%s\n%s\n%s\n%s' \ + ":warning: Fresh Install Tests failed in *${REPOSITORY}* on branch *${BRANCH}*." \ + "*Workflow:* ${WORKFLOW}" \ + "*Run:* <${run_url}|#${RUN_NUMBER} (attempt ${RUN_ATTEMPT})>" \ + "*Failed step:* ${failed_step}") + payload=$(jq --compact-output --null-input --arg text "$message" '{text: $text}') + + curl -sS -X POST \ + --fail \ + --retry 4 \ + --retry-all-errors \ + --retry-max-time 60 \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Content-type: application/json" \ + --data "$payload" \ + "$SLACK_WEBHOOK_URL" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 511fc6d2b2..280d5a5af1 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -14,8 +14,10 @@ on: type: string env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }} NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_SERVER }} + pnpm_config_store_dir: ./node_modules/.pnpm-store jobs: publish: @@ -27,7 +29,7 @@ jobs: attestations: write timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ inputs.version }} fetch-depth: 100 @@ -39,20 +41,20 @@ jobs: id: package-manager-version - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 with: version: ${{ steps.package-manager-version.outputs.stdout }} - - uses: nrwl/nx-set-shas@v3 + - uses: nrwl/nx-set-shas@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" node-version-file: ".nvmrc" - name: Cache NX - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .nx/cache key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} @@ -61,8 +63,6 @@ jobs: nx-${{ env.NX_BRANCH }}- nx- - - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - - name: Install Dependencies & Build run: pnpm install && pnpm build diff --git a/.gitignore b/.gitignore index 92f64ba492..898dcdcc62 100644 --- a/.gitignore +++ b/.gitignore @@ -29,10 +29,15 @@ yarn-error.log* .vercel test-results/ playwright-report/ +blob-report/ release /test-results/ /playwright-report/ +/blob-report/ /playwright/.cache/ .env *.pem .nx/ +# Nightshift plan artifacts (keep out of version control) +.nightshift-plan +.claude \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b16b12af6..112608f8ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,273 @@ +## 0.51.0 (2026-05-14) + +### 🚀 Features + +- Trailing block extension rewrite ([#2733](https://github.com/TypeCellOS/BlockNote/pull/2733)) +- **markdown:** replace unified.js with custom markdown parser/serializer ([#2624](https://github.com/TypeCellOS/BlockNote/pull/2624)) +- **react:** configurable portal targets for floating UI ([#2729](https://github.com/TypeCellOS/BlockNote/pull/2729), [#2692](https://github.com/TypeCellOS/BlockNote/issues/2692)) + +### 🩹 Fixes + +- Pasting plain text from VSCode (BLO-366) ([#2713](https://github.com/TypeCellOS/BlockNote/pull/2713)) +- Parse new lines in `text/plain` as line breaks (BLO-1170) ([#2712](https://github.com/TypeCellOS/BlockNote/pull/2712)) +- Code block PDF export (BLO-987) ([#2725](https://github.com/TypeCellOS/BlockNote/pull/2725)) +- Formatting toolbar opening when inserting file block with `trailingBlock: false` (BLO-860) ([#2704](https://github.com/TypeCellOS/BlockNote/pull/2704)) +- numbered list item decorations missed on initial render ([#2734](https://github.com/TypeCellOS/BlockNote/pull/2734)) +- flicker-free mobile formatting toolbar via CSS custom properties ([#2617](https://github.com/TypeCellOS/BlockNote/pull/2617), [#2616](https://github.com/TypeCellOS/BlockNote/issues/2616)) +- add `bn-thread-orphaned` CSS class to distinguish orphaned threads ([#2737](https://github.com/TypeCellOS/BlockNote/pull/2737), [#2735](https://github.com/TypeCellOS/BlockNote/issues/2735)) +- set width attribute on image and video elements in editor render ([#2740](https://github.com/TypeCellOS/BlockNote/pull/2740), [#2726](https://github.com/TypeCellOS/BlockNote/issues/2726)) +- **a11y:** use figure/figcaption for media block captions ([#2717](https://github.com/TypeCellOS/BlockNote/pull/2717)) +- **ai:** loosen serialization of blocks in columns ([#2716](https://github.com/TypeCellOS/BlockNote/pull/2716), [#2718](https://github.com/TypeCellOS/BlockNote/pull/2718)) +- **core:** trigger codeblock input rule on Enter and place cursor inside ([#2686](https://github.com/TypeCellOS/BlockNote/pull/2686)) +- **core:** preserve list item type when pasting into empty list items ([#2722](https://github.com/TypeCellOS/BlockNote/pull/2722), [#2330](https://github.com/TypeCellOS/BlockNote/issues/2330)) +- **core:** unmount editors in transformPasted tests to prevent unhandled error ([e62880b21](https://github.com/TypeCellOS/BlockNote/commit/e62880b21)) +- **drag-n-drop:** support PDF block drag & drop (BLO-893) ([#2714](https://github.com/TypeCellOS/BlockNote/pull/2714)) +- **i18:** improve french translation for empty toggle list ([#2721](https://github.com/TypeCellOS/BlockNote/pull/2721)) +- **markdown:** emit tight lists when serializing blocks to markdown ([#2715](https://github.com/TypeCellOS/BlockNote/pull/2715)) +- **markdown:** skip placeholder text for empty files ([#434](https://github.com/TypeCellOS/BlockNote/pull/434), [#2719](https://github.com/TypeCellOS/BlockNote/pull/2719)) +- **markdown:** stable round-trip for tables, captions, and audio ([#2720](https://github.com/TypeCellOS/BlockNote/pull/2720)) +- **tests:** stabilize webkit keyboard handler tests with programmatic cursor positioning ([#2746](https://github.com/TypeCellOS/BlockNote/pull/2746)) + +### ❤️ Thank You + +- Cyril G +- Manuel Raynaud @lunika +- Matthew Lipski @matthewlipski +- Movm +- Nick Perez +- Nick the Sick @nperez0111 + +## 0.50.0 (2026-05-04) + +### 🚀 Features + +- Dark mode styling for file block wrapper component (BLO-866) ([#2680](https://github.com/TypeCellOS/BlockNote/pull/2680)) +- Drag hendle menu delete button removes all other blocks in selection (BLO-1007) ([#2683](https://github.com/TypeCellOS/BlockNote/pull/2683)) +- Enter moves selection to cell below in tables (BLO-1006) ([#2685](https://github.com/TypeCellOS/BlockNote/pull/2685)) +- additional heading top padding (BLO-1008) ([#2690](https://github.com/TypeCellOS/BlockNote/pull/2690)) +- Code mark input rule edge case (BLO-938) ([#2698](https://github.com/TypeCellOS/BlockNote/pull/2698)) +- **mantine:** upgrade @mantine/core and @mantine/hooks to v9.0.2 ([#2655](https://github.com/TypeCellOS/BlockNote/pull/2655)) + +### 🩹 Fixes + +- Hardcoded strings in comment components (BLO-1033) ([#2681](https://github.com/TypeCellOS/BlockNote/pull/2681)) +- Color naming & CSS (BLO-946) ([#2684](https://github.com/TypeCellOS/BlockNote/pull/2684)) +- link HTML attributes (BLO-915) ([#2687](https://github.com/TypeCellOS/BlockNote/pull/2687)) +- guard hideMenuIfNotFrozen against undefined view state ([#2694](https://github.com/TypeCellOS/BlockNote/pull/2694), [#2699](https://github.com/TypeCellOS/BlockNote/pull/2699)) +- Clicking comment overlapping link opens link (BLO-1091) ([#2696](https://github.com/TypeCellOS/BlockNote/pull/2696)) +- prevent table row drag from moving an extra adjacent row ([#2703](https://github.com/TypeCellOS/BlockNote/pull/2703)) +- **clipboard:** use ProseMirror selection state for Shadow DOM compatibility ([#2677](https://github.com/TypeCellOS/BlockNote/pull/2677)) + +### ❤️ Thank You + +- jt_fox @LimChaeJune +- Matthew Lipski @matthewlipski +- Nick Perez +- Wieland Lindenthal +- Yousef + +## 0.49.0 (2026-04-24) + +### 🚀 Features + +- simplify links by inlining it to BlockNote ([#2623](https://github.com/TypeCellOS/BlockNote/pull/2623)) +- add Unicode quotation mark input rule for quote blocks ([#2673](https://github.com/TypeCellOS/BlockNote/pull/2673)) + +### 🩹 Fixes + +- Inserting link removes comment & add comment button click buggy ([#2620](https://github.com/TypeCellOS/BlockNote/pull/2620), [#2573](https://github.com/TypeCellOS/BlockNote/issues/2573)) +- `useEditorDOMElement` hook ([#2619](https://github.com/TypeCellOS/BlockNote/pull/2619)) +- text color was not applying to table block ([#2663](https://github.com/TypeCellOS/BlockNote/pull/2663)) +- Drag preview blocking drops when overlapping the editor (BLO-996) ([#2670](https://github.com/TypeCellOS/BlockNote/pull/2670)) +- Drag & drop of blocks without inline content opens formatting toolbar (BLO-1116) ([#2628](https://github.com/TypeCellOS/BlockNote/pull/2628), [#2603](https://github.com/TypeCellOS/BlockNote/issues/2603)) +- save file caption/name on every keystroke instead of on close ([#2575](https://github.com/TypeCellOS/BlockNote/pull/2575)) +- prevent FloatingFocusManager from resetting editor selection ([#2525](https://github.com/TypeCellOS/BlockNote/pull/2525), [#2664](https://github.com/TypeCellOS/BlockNote/pull/2664)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- miadnguyen @miadnguyen +- mianguyen +- Nick Perez +- Yousef + +## 0.48.1 (2026-04-16) + +### 🩹 Fixes + +- make CustomChange compatible with prosemirror-changeset 2.4.1 ([#2647](https://github.com/TypeCellOS/BlockNote/pull/2647)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability (CVE-2025-62718) ([c1ef3018a](https://github.com/TypeCellOS/BlockNote/commit/c1ef3018a)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability ([#2653](https://github.com/TypeCellOS/BlockNote/pull/2653)) +- **docx-exporter:** omit w:lang when no locale provided instead of defaulting to en-US ([#2651](https://github.com/TypeCellOS/BlockNote/pull/2651)) + +### ❤️ Thank You + +- Claude Opus 4.6 (1M context) +- Nick Perez +- Nick the Sick +- Stephan Meijer @StephanMeijer + +## 0.48.0 (2026-04-13) + +### 🚀 Features + +- upgrade shiki to v4 and prosemirror-highlight to v0.15.1 ([#2625](https://github.com/TypeCellOS/BlockNote/pull/2625)) +- upgrade nx to 22.6.4 and liveblocks to 3.17.0 ([#2627](https://github.com/TypeCellOS/BlockNote/pull/2627)) + +### 🩹 Fixes + +- Image block selection clears on mouse leave in Safari ([#2613](https://github.com/TypeCellOS/BlockNote/pull/2613)) +- Backspace bug when current block is empty and previous block's last child is empty ([#2610](https://github.com/TypeCellOS/BlockNote/pull/2610)) +- allow using latest @tiptap/extension-link version ([1ae8de713](https://github.com/TypeCellOS/BlockNote/commit/1ae8de713)) +- restore depth guard in getParentBlockInfo to prevent RangeError (blo-1103) ([#2585](https://github.com/TypeCellOS/BlockNote/pull/2585)) +- pin better-auth to ~1.4.x to fix docs build ([bda30458a](https://github.com/TypeCellOS/BlockNote/commit/bda30458a)) +- hide side menu on scroll instead of overflow hacks ([#2630](https://github.com/TypeCellOS/BlockNote/pull/2630), [#2043](https://github.com/TypeCellOS/BlockNote/issues/2043)) +- disable default UI when no components context is found ([#2611](https://github.com/TypeCellOS/BlockNote/pull/2611)) +- add .js extension to fast-deep-equal ESM import ([#2641](https://github.com/TypeCellOS/BlockNote/pull/2641)) +- placeholder when overflowing now wraps ([#2291](https://github.com/TypeCellOS/BlockNote/pull/2291)) +- **core:** fix unnesting blocks with siblings (BLO-1017) ([#2601](https://github.com/TypeCellOS/BlockNote/pull/2601)) +- **core:** backspace mid-text next to columnList moves block BLO-1126 ([#2629](https://github.com/TypeCellOS/BlockNote/pull/2629)) + +### 🔥 Performance + +- optimize plugin traversals for large documents BLO-1111 ([#2600](https://github.com/TypeCellOS/BlockNote/pull/2600)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Claude Opus 4.6 (1M context) +- hedi-ghodhbane @hedi-ghodhbane +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yousef + +## 0.47.3 (2026-03-25) + +### 🩹 Fixes + +- **core:** preserve whitespace edge cases but collapse html formatting newlines (BLO-1065) ([#2551](https://github.com/TypeCellOS/BlockNote/pull/2551), [#2230](https://github.com/TypeCellOS/BlockNote/issues/2230)) + +### ❤️ Thank You + +- Yousef + +## 0.47.2 (2026-03-20) + +### 🩹 Fixes + +- use
/ for toggle block HTML export ([#2524](https://github.com/TypeCellOS/BlockNote/pull/2524)) +- remove @hocuspocus/provider peer dependency by inlining tiptap comment types BLO-1064 ([#2564](https://github.com/TypeCellOS/BlockNote/pull/2564)) +- **core:** slash menu fails in custom blocks after space BLO-1036 ([#2553](https://github.com/TypeCellOS/BlockNote/pull/2553)) +- **i18n:** fix typo in russian translation ([#2560](https://github.com/TypeCellOS/BlockNote/pull/2560)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Drone +- Yousef + +## 0.47.1 (2026-03-02) + +### 🩹 Fixes + +- typeerror cannot read properties of undefined ([#2522](https://github.com/TypeCellOS/BlockNote/pull/2522)) +- handle more delete key cases ([#2126](https://github.com/TypeCellOS/BlockNote/pull/2126)) +- add delay for `data-active` in collab cursors ([#2383](https://github.com/TypeCellOS/BlockNote/pull/2383)) +- disable slash menu in table content #2408 ([#2504](https://github.com/TypeCellOS/BlockNote/pull/2504), [#2408](https://github.com/TypeCellOS/BlockNote/issues/2408)) +- **ai:** selections broken due to floating-ui focus manager ([#2527](https://github.com/TypeCellOS/BlockNote/pull/2527)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- Nick Perez +- Yousef + +## 0.47.0 (2026-02-23) + +### 🚀 Features + +- update suggestion menu component ([#2397](https://github.com/TypeCellOS/BlockNote/pull/2397)) +- **i18n:** add Persian (fa) localization support ([#2447](https://github.com/TypeCellOS/BlockNote/pull/2447)) +- **i18n:** add Uzbek (uz) localization support ([#2506](https://github.com/TypeCellOS/BlockNote/pull/2506)) + +### 🩹 Fixes + +- prevent nested bullet list icon rendering as emoji on iOS 18+ ([#2394](https://github.com/TypeCellOS/BlockNote/pull/2394), [#2399](https://github.com/TypeCellOS/BlockNote/pull/2399)) +- ignore drag & drop from unrelated events #1968 ([#2346](https://github.com/TypeCellOS/BlockNote/pull/2346), [#1968](https://github.com/TypeCellOS/BlockNote/issues/1968)) +- disable checkbox when editor is not editable #2406 ([#2448](https://github.com/TypeCellOS/BlockNote/pull/2448), [#2406](https://github.com/TypeCellOS/BlockNote/issues/2406)) +- Backspace/enter behaviour in empty block with children ([#2451](https://github.com/TypeCellOS/BlockNote/pull/2451)) +- handle pasting into table cells better, by collapsing their content to inline #2410 ([#2449](https://github.com/TypeCellOS/BlockNote/pull/2449), [#2410](https://github.com/TypeCellOS/BlockNote/issues/2410)) +- **accessibility:** ai combobox aria-activedescendant ([#2413](https://github.com/TypeCellOS/BlockNote/pull/2413)) +- **ai:** no more scrolling to top when opening AI menu ([#2503](https://github.com/TypeCellOS/BlockNote/pull/2503)) +- **docs:** unicode char not rendered in bug template ([f13e270be](https://github.com/TypeCellOS/BlockNote/commit/f13e270be)) + +### ❤️ Thank You + +- Cyril G @Ovgodd +- Dex Devlon @bxff +- Matthew Lipski @matthewlipski +- MDSAM05 @MDSAM05 +- Mohammad RAHMANI @Mrahmani71 +- Nick Perez +- Ogabek @OgabekYuldoshev +- Wouter Vroege +- Yousef + +## 0.46.2 (2026-01-27) + +### 🩹 Fixes + +- deep merge floatingUIOptions using nested spread operators ([#2310](https://github.com/TypeCellOS/BlockNote/pull/2310)) +- Visual differences between live editor and rendered exported HTML ([#2348](https://github.com/TypeCellOS/BlockNote/pull/2348)) +- `BlockNoteViewEditor` mismatched editable value ([#2357](https://github.com/TypeCellOS/BlockNote/pull/2357)) +- add `font-synthesis` for italic & bold in fonts that don't have them specified #2325 ([#2354](https://github.com/TypeCellOS/BlockNote/pull/2354), [#2325](https://github.com/TypeCellOS/BlockNote/issues/2325)) +- disable code block language selector when editor is not editable ([#2351](https://github.com/TypeCellOS/BlockNote/pull/2351)) +- table handles would crash ([#2384](https://github.com/TypeCellOS/BlockNote/pull/2384)) +- update CreateLinkButton to be able to toggle popover visibility ([#2316](https://github.com/TypeCellOS/BlockNote/pull/2316), [#2313](https://github.com/TypeCellOS/BlockNote/issues/2313)) +- add context,nestingLevel to toExternalHTML ([#2373](https://github.com/TypeCellOS/BlockNote/pull/2373)) +- **ai:** re-enable flipping the AIMenu when there is not enough space #2245 ([#2247](https://github.com/TypeCellOS/BlockNote/pull/2247), [#2245](https://github.com/TypeCellOS/BlockNote/issues/2245)) +- **link-toolbar:** prevent Enter from submitting during IME composition ([#2361](https://github.com/TypeCellOS/BlockNote/pull/2361)) + +### ❤️ Thank You + +- hanios123 +- Jean-Baptiste PENRATH +- Matthew Lipski @matthewlipski +- Nick Perez +- Shohei Yoshida @ysds +- Yousef + +## 0.46.1 (2026-01-10) + +This was a version bump only, there were no code changes. + +## 0.46.0 (2026-01-08) + +### 🚀 Features + +- add data-nesting-level to HTML export ([#2329](https://github.com/TypeCellOS/BlockNote/pull/2329)) +- migrate to ai sdk 6 ([#2328](https://github.com/TypeCellOS/BlockNote/pull/2328)) + +### 🩹 Fixes + +- emojipicker can sometimes fail to mount ([575b81cec](https://github.com/TypeCellOS/BlockNote/commit/575b81cec)) +- LinkToolbar Event Listener leak ([#2335](https://github.com/TypeCellOS/BlockNote/pull/2335)) +- when you convert a block into checkListItem via inputRule, it should transfer its content into checkListItem content ([#2331](https://github.com/TypeCellOS/BlockNote/pull/2331)) +- do not return focus back to menu ([484d7da36](https://github.com/TypeCellOS/BlockNote/commit/484d7da36)) +- arrow up on a checklist item should move to the element above BLO-362 ([#2306](https://github.com/TypeCellOS/BlockNote/pull/2306)) +- getPos race condition in React StrictMode ([#2311](https://github.com/TypeCellOS/BlockNote/pull/2311)) +- adjust input rules to be more tolerant to starting whitespace ([#2341](https://github.com/TypeCellOS/BlockNote/pull/2341)) +- **ai:** make sure ShowSelection works ([#2297](https://github.com/TypeCellOS/BlockNote/pull/2297)) +- **xl-email-exporter:** remove redundant sections in email export ([#2323](https://github.com/TypeCellOS/BlockNote/pull/2323)) + +### ❤️ Thank You + +- Nick Perez +- Nick the Sick @nperez0111 +- supernova @tmpluto +- Yousef + ## 0.45.0 (2025-12-17) ### 🚀 Features diff --git a/docs/.env.local.example b/docs/.env.local.example index cb90db5a97..a2dba36b4b 100644 --- a/docs/.env.local.example +++ b/docs/.env.local.example @@ -30,4 +30,6 @@ BETTER_AUTH_URL=http://localhost:3000 # SENTRY_AUTH_TOKEN= NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_API_KEY= -NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_BASE_URL= \ No newline at end of file +NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_BASE_URL= + +TURNSTILE_SECRET_KEY= \ No newline at end of file diff --git a/docs/.eslintrc.json b/docs/.eslintrc.json deleted file mode 100644 index 3f47543412..0000000000 --- a/docs/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "import/extensions": 0 - } -} diff --git a/docs/.gitignore b/docs/.gitignore index 649c9b9df2..c12953bff8 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,45 +1,32 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies +# deps /node_modules -/.pnp -.pnp.js -.yarn/install-state.gz -# testing -/coverage +# generated content +.source -# next.js +# test & build +/coverage /.next/ /out/ - -# production /build +*.tsbuildinfo # misc .DS_Store *.pem - -# debug +/.pnp +.pnp.js npm-debug.log* yarn-debug.log* yarn-error.log* -# local env files +# others .env*.local - -# vercel .vercel - -# typescript -*.tsbuildinfo next-env.d.ts - -# database -*.db - # Sentry Config File .env.sentry-build-plugin + /content/examples/*/* /components/example/generated/ -/.source/ \ No newline at end of file +sqlite.db diff --git a/docs/app/(auth)/layout.tsx b/docs/app/(auth)/layout.tsx new file mode 100644 index 0000000000..715e32a774 --- /dev/null +++ b/docs/app/(auth)/layout.tsx @@ -0,0 +1,10 @@ +import { HomeLayout } from "@/components/fumadocs/layout/home"; +import { baseOptions } from "@/lib/layout.shared"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + +
{children}
+
+ ); +} diff --git a/docs/app/(auth)/signin/page.tsx b/docs/app/(auth)/signin/page.tsx new file mode 100644 index 0000000000..b21d007847 --- /dev/null +++ b/docs/app/(auth)/signin/page.tsx @@ -0,0 +1,18 @@ +import AuthenticationPage from "@/components/AuthenticationPage"; +import { getFullMetadata } from "@/lib/getFullMetadata"; +import { Suspense } from "react"; + +export const metadata = getFullMetadata({ + title: "Sign In", + path: "/signin", +}); + +// Suspense + imported AuthenticationPage because AuthenticationPage is a client component +// https://nextjs.org/docs/app/api-reference/functions/use-search-params#static-rendering +export default function Page() { + return ( + + + + ); +} diff --git a/docs/app/(auth)/signin/password/page.tsx b/docs/app/(auth)/signin/password/page.tsx new file mode 100644 index 0000000000..d998c4db48 --- /dev/null +++ b/docs/app/(auth)/signin/password/page.tsx @@ -0,0 +1,17 @@ +import AuthenticationPage from "@/components/AuthenticationPage"; +import { Metadata } from "next"; +import { Suspense } from "react"; + +export const metadata: Metadata = { + title: "Password Login", +}; + +// Suspense + imported AuthenticationPage because AuthenticationPage is a client component +// https://nextjs.org/docs/app/api-reference/functions/use-search-params#static-rendering +export default function Page() { + return ( + + + + ); +} diff --git a/docs/app/(auth)/signup/page.tsx b/docs/app/(auth)/signup/page.tsx new file mode 100644 index 0000000000..04aceaa07a --- /dev/null +++ b/docs/app/(auth)/signup/page.tsx @@ -0,0 +1,18 @@ +import AuthenticationPage from "@/components/AuthenticationPage"; +import { getFullMetadata } from "@/lib/getFullMetadata"; +import { Suspense } from "react"; + +export const metadata = getFullMetadata({ + title: "Sign Up", + path: "/signup", +}); + +// Suspense + imported AuthenticationPage because AuthenticationPage is a client component +// https://nextjs.org/docs/app/api-reference/functions/use-search-params#static-rendering +export default function Page() { + return ( + + + + ); +} diff --git a/docs/app/(home)/_components/BlockCatalog.tsx b/docs/app/(home)/_components/BlockCatalog.tsx new file mode 100644 index 0000000000..72804ec79e --- /dev/null +++ b/docs/app/(home)/_components/BlockCatalog.tsx @@ -0,0 +1,108 @@ +"use client"; +import { + AudioWaveform, + ChevronRight, + Code2, + FileText, + Heading, + Image, + List, + ListOrdered, + ListTodo, + Minus, + Pilcrow, + Puzzle, + Quote, + Table, + Video, +} from "lucide-react"; +import React from "react"; + +const BlockCatalogItem: React.FC<{ name: string; icon: React.ReactNode }> = ({ + name, + icon, +}) => ( +
+
+
+ {icon} +
+ + {name} + +
+); + +export const BlockCatalog: React.FC = () => { + return ( +
+ {/* Subtle decorative elements */} +
+
+
+
+
+ +
+
+
+ 🧩 +
+

+ Build anything, block by block. +

+

+ Every BlockNote document is a collection of blocks—headings, lists, + images, and more. Use the built-in blocks, customize them to fit + your needs, or create entirely new ones. +

+
+ +
+ } + /> + } + /> + } /> + } + /> + } + /> + } + /> + } /> + } /> + } + /> + } /> + } /> + } /> + } + /> + } + /> + } + /> +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/DigitalCommons.tsx b/docs/app/(home)/_components/DigitalCommons.tsx new file mode 100644 index 0000000000..c4cc4e941e --- /dev/null +++ b/docs/app/(home)/_components/DigitalCommons.tsx @@ -0,0 +1,127 @@ +"use client"; +import Link from "next/link"; +import React, { useRef, useState } from "react"; + +export const DigitalCommons: React.FC = () => { + const videoRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + + const handlePlayPause = () => { + if (videoRef.current) { + if (isPlaying) { + videoRef.current.pause(); + } else { + videoRef.current.play(); + } + setIsPlaying(!isPlaying); + } + }; + + return ( +
+ {/* Warm gradient overlay to harmonize with cream hero */} +
+ {/* Top edge gradient for smoother transition */} +
+ +
+ {/* Asymmetric layout: content + video (vertically centered) */} +
+ {/* Left: Editorial content */} +
+ {/* Eyebrow with EU flag only */} +
+ 🇪🇺 + + Digital Commons + +
+ + {/* Headline - editorial style */} +

+ Three nations choose
+ + open source + {" "} + to power +
+ their digital future. +

+ + {/* Short punchy copy */} +

+ France, Germany, and the Netherlands partner to build{" "} + + Docs + + , a collaborative writing tool for thousands of public servants.{" "} + BlockNote is the engine. +

+ + {/* Compelling social proof - simpler */} +

+ "Building Digital Commons means better tools, data + sovereignty, and shared progress." +

+ + {/* CTA */} + + Partner with us + + +
+ + {/* Right: Video - vertically centered */} +
+ {/* Glow effect */} +
+ +
+ + + {/* Play button overlay */} + {!isPlaying && ( + + )} +
+
+
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FAQ.tsx b/docs/app/(home)/_components/FAQ.tsx new file mode 100644 index 0000000000..158c5c691a --- /dev/null +++ b/docs/app/(home)/_components/FAQ.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +const faqs = [ + { + question: "Isn't it easier to use a Headless editor framework?", + answer: + "There are a number of really powerful headless text editor frameworks available. In fact, BlockNote is built on Prosemirror and TipTap. However, even when using a headless library, it takes several months and requires deep expertise to build a fully-featured editor with a polished UI that your users expect.", + }, + { + question: "Is BlockNote ready for production use?", + answer: + "BlockNote is used by dozens of companies in production, ranging from startups to large enterprises and public institutions. Also, we didn't reinvent the wheel. The core editor is built on top of Prosemirror - a battle tested framework that powers software from Atlassian, Gitlab, the New York Times, and many others.", + }, + { + question: "Can I add my own extensions to BlockNote?", + answer: + "BlockNote comes with lot of functionality out-of-the-box, but we understand that every use case is different. You can easily customize the built-in UI Components, or create your own custom Blocks, Inline Content, and Styles. If you want to go even further, you can extend the core editor with additional Prosemirror or TipTap plugins.", + }, + { + question: "Is BlockNote really free?", + answer: + "100% of BlockNote is open source. We offer consultancy, support services and commercial licenses for specific XL packages to help sustain BlockNote. Explore our pricing page for more details.", + }, +]; + +export const FAQ: React.FC = () => { + return ( +
+
+
+

+ Questions? +

+
+ +
+ {faqs.map((faq, index) => ( +
+

+ {faq.question} +

+

+ {faq.answer} +

+
+ ))} +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FeatureAI.tsx b/docs/app/(home)/_components/FeatureAI.tsx new file mode 100644 index 0000000000..6f87e80e6b --- /dev/null +++ b/docs/app/(home)/_components/FeatureAI.tsx @@ -0,0 +1,63 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureAI: React.FC = () => { + const [activeTab, setActiveTab] = useState<"toolbar" | "models" | "human">( + "toolbar", + ); + + const content: Record = { + toolbar: { + type: "video", + src: "/video/ai-select.mp4", + className: "px-4", + }, + models: { + type: "image", + src: "/img/screenshots/home/any_model.png", + alt: "Bring Any Model", + }, + human: { + type: "image", + src: "/img/screenshots/home/human_in_the_loop.png", + alt: "Human in the Loop", + }, + }; + + const tabs = [ + { + id: "toolbar", + icon: , + label: "AI in the Editor", + description: + "Context-aware completions and edits directly in the document.", + }, + { + id: "models", + icon: 🔌, + label: "Bring Any Model", + description: "Connect OpenAI, Anthropic, or your own endpoints.", + }, + { + id: "human", + icon: 🤝, + label: "Human in the Loop", + description: "Users accept, reject, or refine AI suggestions.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={true} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureCollab.tsx b/docs/app/(home)/_components/FeatureCollab.tsx new file mode 100644 index 0000000000..3553d8255f --- /dev/null +++ b/docs/app/(home)/_components/FeatureCollab.tsx @@ -0,0 +1,65 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureCollab: React.FC<{ + code: { realtime: string }; +}> = ({ code }) => { + const [activeTab, setActiveTab] = useState< + "realtime" | "comments" | "suggestions" + >("realtime"); + + const content: Record = { + realtime: { + type: "code", + file: "CollaborativeEditor.tsx", + code: code.realtime, + }, + comments: { + type: "image", + src: "/img/screenshots/home/comments.png", + alt: "Comments", + }, + suggestions: { + type: "image", + src: "/img/screenshots/home/versioning.png", + alt: "Versioning", + }, + }; + + const tabs = [ + { + id: "realtime", + icon: 👯, + label: "Real-Time Sync", + description: "Yjs-powered with automatic conflict resolution.", + }, + { + id: "comments", + icon: 💬, + label: "Comments", + description: "Inline threads and mentions keep conversations in context.", + }, + { + id: "suggestions", + icon: 📝, + label: "Suggestions & Versioning (coming soon)", + description: + "Track changes, accept or reject edits. Full document history.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={false} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureDX.tsx b/docs/app/(home)/_components/FeatureDX.tsx new file mode 100644 index 0000000000..a98353e0e1 --- /dev/null +++ b/docs/app/(home)/_components/FeatureDX.tsx @@ -0,0 +1,64 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureDX: React.FC<{ + code: { theming: string; extend: string }; +}> = ({ code }) => { + const [activeTab, setActiveTab] = useState<"types" | "theming" | "extend">( + "types", + ); + + const content: Record = { + types: { + type: "image", + src: "/img/screenshots/home/code-typescript-support.png", + alt: "Type-Safe Schema", + }, + theming: { + type: "code", + file: "Editor.tsx", + code: code.theming, + }, + extend: { + type: "code", + file: "CustomBlock.tsx", + code: code.extend, + }, + }; + + const tabs = [ + { + id: "types", + icon: 📐, + label: "Type-Safe", + description: "Full autocompletion and type inference for custom schemas.", + }, + { + id: "theming", + icon: 🎨, + label: "Bring your Design System", + description: "Works with Mantine, shadcn/ui, or go headless.", + }, + { + id: "extend", + icon: 🔧, + label: "Extend Everything", + description: "Create custom blocks, inline content, menus and more.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={true} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureSection.tsx b/docs/app/(home)/_components/FeatureSection.tsx new file mode 100644 index 0000000000..ef2c62eea1 --- /dev/null +++ b/docs/app/(home)/_components/FeatureSection.tsx @@ -0,0 +1,101 @@ +import React from "react"; + +interface FeatureTab { + id: string; + icon: React.ReactNode; + label: string; + description: string; +} + +interface FeatureSectionProps { + title: string; + description: string; + tabs: FeatureTab[]; + activeTabId: string; + onTabChange: (id: string) => void; + // The content to display on the right side (Visual or Code) + children: React.ReactNode; + // Optional: Swap order for visual variety (Left/Right) + reverse?: boolean; +} + +export const FeatureSection: React.FC = ({ + title, + description, + tabs, + activeTabId, + onTabChange, + children, + reverse = false, +}) => { + return ( +
+ {/* Left Text & Tabs */} +
+

+ {title} +

+

+ {description} +

+ +
+ {tabs.map((tab) => { + const isActive = activeTabId === tab.id; + // Dynamic styles based on active state could be passed or handled here + // For simplicity, we'll use a generic active style or specific color logic if needed. + // But CodePlayground had specific colors (purple, amber, blue). + // Let's rely on the parent or use a generic active style here for now, + // or we can add a 'color' prop to FeatureTab if we want distinct colors per tab. + + return ( + + ); + })} +
+
+ + {/* Right Visual */} +
+ {/*
*/} +
+ {children} +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FeatureUX.tsx b/docs/app/(home)/_components/FeatureUX.tsx new file mode 100644 index 0000000000..3bfe2d00ff --- /dev/null +++ b/docs/app/(home)/_components/FeatureUX.tsx @@ -0,0 +1,60 @@ +"use client"; +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; +export const FeatureUX: React.FC = () => { + const [activeTab, setActiveTab] = useState<"components" | "ai" | "blocks">( + "components", + ); + + const content: Record = { + components: { + type: "video", + src: "/video/batteries-included.mp4", + }, + ai: { + type: "video", + src: "/video/ai-select.mp4", + className: "px-4", + }, + blocks: { + type: "video", + src: "/video/dragdrop.mp4", + }, + }; + + const tabs = [ + { + id: "components", + icon: 🔋, + label: "Ready to Use", + description: + "Slash menus, formatting toolbars, and drag handles work instantly.", + }, + { + id: "ai", + icon: , + label: "AI Assistance", + description: "Write and redact content with AI.", + }, + { + id: "blocks", + icon: 🧱, + label: "Block-Based", + description: "Drag, drop, and nest content blocks.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={false} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FrameworkPill.tsx b/docs/app/(home)/_components/FrameworkPill.tsx new file mode 100644 index 0000000000..eef63da2ba --- /dev/null +++ b/docs/app/(home)/_components/FrameworkPill.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +export const FrameworkPill: React.FC<{ + name: string; + color: string; + icon?: React.ReactNode; +}> = ({ name, color, icon }) => ( +
+ {icon ||
} + {name} +
+); diff --git a/docs/app/(home)/_components/Hero.tsx b/docs/app/(home)/_components/Hero.tsx new file mode 100644 index 0000000000..4e383ce2f3 --- /dev/null +++ b/docs/app/(home)/_components/Hero.tsx @@ -0,0 +1,69 @@ +import Link from "next/link"; +import React from "react"; +import { HeroVideo } from "./HeroVideo"; +import { TextLoop } from "./TextLoop"; + +export const Hero: React.FC = () => { + const BADGES = [ + { icon: "⭐️", text: "100k+ weekly installs" }, + { icon: "🛡️", text: "100% Open source & self-hostable" }, + { icon: "✨", text: "AI Ready" }, + ]; + + return ( +
+ {/* Hero Section */} +
+ {/* Passive Neural Background */} +
+ {/* Badge */} + + {BADGES.map((badge, index) => ( +
+ + {badge.icon} + {badge.text} + +
+ ))} +
+ +

+ Build a Notion-style{" "} + editor in minutes. +

+

+ The AI-native, open source rich + text editor for React. Add a{" "} + fully customizable modern block-based editing + experience to your product that users will love. +

+ +
+ + View Demo + + → + + + + Documentation + +
+
+
+ +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/HeroVideo.tsx b/docs/app/(home)/_components/HeroVideo.tsx new file mode 100644 index 0000000000..a36cccbe73 --- /dev/null +++ b/docs/app/(home)/_components/HeroVideo.tsx @@ -0,0 +1,77 @@ +"use client"; +import Link from "next/link"; +import React from "react"; + +export const HeroVideo: React.FC = () => { + return ( + <> + +
+ {/* Editor Placeholder */} + {/* Editor Preview */} + +
+
+ {/* Browser Chrome */} +