diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..87697e5b29 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Keep the e2e image build context lean and Linux-clean: never copy host +# (macOS) node_modules or build outputs — deps are installed fresh inside the +# image. What actually lands in the image is selected explicitly by the COPY +# steps in tests/Dockerfile (manifests + example apps), not by exclusions here; +# these entries just stop the heavy/irrelevant trees from bloating the context. +**/node_modules +**/dist +**/types +**/.vite +**/.vite-plus +**/*.tsbuildinfo +**/.DS_Store +.git +**/test-results +**/blob-report +**/playwright-report +tests/.vitest-attachments diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000000..bce33191f8 --- /dev/null +++ b/.env.sample @@ -0,0 +1,2 @@ +export NX_SELF_HOSTED_REMOTE_CACHE_SERVER=https://cache.nickthesick.com +export NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN=g8@ucL8em4*Z9TKXDY9OEX@!upf^Nz9 \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index c0a4c78b89..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,59 +0,0 @@ -const typeScriptExtensions = [".ts", ".cts", ".mts", ".tsx"]; - -const allExtensions = [...typeScriptExtensions, ".js", ".jsx"]; - -module.exports = { - root: true, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "react-app", - "react-app/jest", - ], - parser: "@typescript-eslint/parser", - plugins: ["import", "@typescript-eslint"], - settings: { - "import/extensions": allExtensions, - "import/external-module-folders": ["node_modules", "node_modules/@types"], - "import/parsers": { - "@typescript-eslint/parser": typeScriptExtensions, - }, - "import/resolver": { - node: { - extensions: allExtensions, - }, - }, - }, - ignorePatterns: ["**/ui/*"], - rules: { - "no-console": "error", - curly: 1, - "import/no-extraneous-dependencies": [ - "error", - { - devDependencies: true, - optionalDependencies: false, - peerDependencies: false, - bundledDependencies: false, - }, - ], - // would be nice to enable these rules later, but they are too noisy right now - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-ts-comment": "off", - "import/no-cycle": "error", - // doesn't work: - // "import/no-restricted-paths": [ - // "error", - // { - // zones: [ - // { - // target: "./src/**/*", - // from: "./types/**/*", - // message: "Import from this module to types is not allowed.", - // }, - // ], - // }, - // ], - }, -}; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index fec535d794..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,20 +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://github.com/sponsors/YousefED) 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..d431145bc6 --- /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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index a76f4cd708..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://github.com/sponsors/YousefED) and would appreciate if you could look into this sooner than later 💖 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..4cd39c8b21 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,68 @@ +version: 2 +updates: + - package-ecosystem: "npm" + versioning-strategy: "lockfile-only" + directories: + - "/" + - "/packages/*" + schedule: + interval: "weekly" + labels: + - "npm dependencies" + commit-message: + prefix: "chore" + allow: + # @tiptap packages + - dependency-name: "@tiptap/core" + - dependency-name: "@tiptap/pm" + - dependency-name: "@tiptap/react" + - dependency-name: "@tiptap/extensions" + - dependency-name: "@tiptap/extension-bold" + - dependency-name: "@tiptap/extension-code" + - dependency-name: "@tiptap/extension-horizontal-rule" + - dependency-name: "@tiptap/extension-italic" + + - 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-highlight" + - dependency-name: "prosemirror-model" + - dependency-name: "prosemirror-state" + - dependency-name: "prosemirror-tables" + - dependency-name: "prosemirror-transform" + - dependency-name: "prosemirror-view" + # react packages + - dependency-name: "react" + - dependency-name: "react-dom" + # 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: + - "@tiptap/*" + - "prosemirror-*" + - "react" + - "react-dom" + - "yjs" + - "y-prosemirror" + cooldown: + default-days: 7 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" + include: "scope" + cooldown: + default-days: 7 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..9870532e04 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ +# Summary + + + +## Rationale + + + +## Changes + + + +## Impact + + + +## Testing + + + +## Screenshots/Video + + + +## Checklist + +- [ ] Code follows the project's coding standards. +- [ ] Unit tests covering the new feature have been added. +- [ ] All existing tests pass. +- [ ] The documentation has been updated to reflect the new feature + +## Additional Notes + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16cdab8558..2f8f7dfc38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,79 +6,196 @@ on: pull_request: types: [opened, synchronize, reopened, edited] +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: name: Build runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - node-version: "20.x" + fetch-depth: 100 + persist-credentials: false - - name: Cache node modules - uses: actions/cache@v4 - env: - cache-name: cache-node-modules + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - name: cache playwright - id: playwright-cache - uses: actions/cache@v4 - with: - path: ~/.cache/ms-playwright - key: pw-new-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} + node-version-file: ".node-version" + cache: true - name: Install Dependencies - run: npm install - - - name: Bootstrap packages - run: npm run bootstrap + run: vp install - name: Lint packages - run: npm run lint - env: - CI: true + run: vp lint - name: Build packages - run: npm run build - env: - CI: true + run: vp run -r build - name: Run unit tests - run: npm run test - env: - CI: true + run: vp run -r test + + - name: Run Next.js integration test (production build) + run: NEXTJS_TEST_MODE=build vp test run src/unit/nextjs/serverUtil.test.ts + working-directory: tests + + - name: Upload webpack stats artifact (editor) + uses: relative-ci/agent-upload-artifact-action@a2b5741b4f7e6a989c84ec1a3059696b23c152e5 # v2 + with: + webpackStatsFile: ./playground/dist/webpack-stats.json + artifactName: relative-ci-artifacts-editor + + - name: Soft release + id: soft-release + run: vp dlx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact + + e2e: + # Vitest Browser Mode runs in the Playwright Linux container — the same + # environment as the local Docker run — so behaviour matches local dev. + # The suite resolves every `@blocknote/*` import to its `src/` via the + # aliases in `tests/vite.config.browser.ts` (vite transpiles the package + # sources on the fly), so the packages do NOT need to be built to `dist` + # first — `vp install` + the checked-out sources are enough. + name: "E2E - ${{ matrix.browser }} (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})" + runs-on: ubuntu-latest + timeout-minutes: 30 + container: + image: mcr.microsoft.com/playwright:v1.60.0-noble + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] + shardIndex: [1, 2] + shardTotal: [2] + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + fetch-depth: 100 + persist-credentials: false + + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + cache: true + + - name: Install dependencies + run: vp install + + # No preview server: Vitest Browser Mode serves the tests + mounted example + # apps itself. `--browser` selects this matrix job's browser and + # `--shard=/` splits that browser's test files across two + # parallel machines. Each shard records a machine-readable `blob` report + # (named per browser+shard so they don't collide); the `merge-reports` job + # stitches every browser's shards into one HTML report afterwards. + - name: Run e2e tests (${{ matrix.browser }} ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) + run: HOME=/root vp test -c vite.config.browser.ts --run --browser ${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=default --reporter=blob --outputFile.blob=blob-report/blob-${{ matrix.browser }}-${{ matrix.shardIndex }}.json + working-directory: tests - - name: Run server - run: npm run start:built & npx wait-on http://localhost:3000 - env: - CI: true + - name: Upload blob report (${{ matrix.browser }} ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} + with: + name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/blob-report/ + retention-days: 1 + + - name: Upload failure artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ failure() }} + with: + name: e2e-attachments-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/.vitest-attachments/ + retention-days: 7 + + merge-reports: + # Stitch every browser+shard blob report into a single navigable HTML + # report (the Vitest equivalent of `playwright merge-reports`). Runs even + # when a shard failed, so the report always covers every browser. + name: "E2E Report" + runs-on: ubuntu-latest + needs: e2e + if: ${{ !cancelled() }} + timeout-minutes: 15 + # Same container as the e2e shards: `--mergeReports` doesn't run tests or + # launch browsers, but the HTML reporter still resolves the test files' + # browser environment, so the browser config must stay enabled (and present). + container: + image: mcr.microsoft.com/playwright:v1.60.0-noble + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + fetch-depth: 100 + persist-credentials: false + + - uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + cache: true + + - name: Install dependencies + run: vp install - - name: Install Playwright - run: npm run install-playwright + # Gather every shard's blob into one directory (blob-report-chromium-1, …). + - name: Download blob reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + path: tests/blob-report + pattern: blob-report-* + merge-multiple: true - - name: Run Playwright tests - working-directory: ./tests - run: npx playwright test + # Re-emit a single HTML report from the merged blobs (no tests are run). + - name: Merge into HTML report + run: HOME=/root vp test -c vite.config.browser.ts --mergeReports=blob-report --reporter=html + working-directory: tests - - uses: actions/upload-artifact@v4 - if: always() + - name: Upload merged HTML report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + if: ${{ !cancelled() }} with: - name: playwright-report + name: e2e-report path: tests/playwright-report/ - retention-days: 30 + retention-days: 7 - - name: Upload webpack stats artifact (editor) - uses: relative-ci/agent-upload-artifact-action@v1 + deploy-report: + # Publish the merged report to a GitHub Pages PR preview and comment the + # link on the PR (Vercel-style). Pages serves it over HTTP, so the report's + # data loads correctly — unlike opening the downloaded artifact over file:// + # (the @vitest/ui report fetches its data and is blocked by CORS there). + # + # Default `needs` semantics: this runs only when `merge-reports` succeeded + # (i.e. a report exists) — but regardless of whether the tests passed, so a + # red run still gets a navigable preview. Skipped for fork PRs, whose + # read-only token can't push to gh-pages or comment (they keep the artifact). + name: "E2E Report Preview" + runs-on: ubuntu-latest + needs: merge-reports + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - webpackStatsFile: ./playground/dist/webpack-stats.json - artifactName: relative-ci-artifacts-editor + persist-credentials: false + + - name: Download merged report + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: e2e-report + path: e2e-report + + # Deploys to the `gh-pages` branch under `pr-preview/pr-/` and posts a + # sticky comment with the URL. The companion `pr-preview-cleanup` workflow + # removes it when the PR closes. + - name: Deploy report to PR preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1 + with: + source-dir: e2e-report + preview-branch: gh-pages + umbrella-dir: pr-preview + action: deploy diff --git a/.github/workflows/fresh-install-tests.yml b/.github/workflows/fresh-install-tests.yml new file mode 100644 index 0000000000..a1b441678d --- /dev/null +++ b/.github/workflows/fresh-install-tests.yml @@ -0,0 +1,148 @@ +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 + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + fresh-install-unit-tests: + name: Unit Tests (Fresh Dep Resolution) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - id: checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - id: setup_vp + uses: voidzero-dev/setup-vp@2dec1e33f4ab2c6d5bce1b0c4607961bb1a3f7a1 # v1 + with: + node-version-file: ".node-version" + # Intentionally no install cache — we want fresh prod dep resolution. + cache: false + + - id: install_dependencies + name: Install dependencies + run: vp 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. + # NB: this uses pnpm directly because vp doesn't expose a `--prod`-only + # update flow; setup-vp installs pnpm on PATH so this still works. + 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: vp run -r build + + - id: run_unit_tests + name: Run unit tests + run: vp run -r test + + - 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 }} + STEPS_CHECKOUT_OUTCOME: ${{ steps.checkout.outcome }} + STEPS_SETUP_VP_OUTCOME: ${{ steps.setup_vp.outcome }} + STEPS_INSTALL_DEPENDENCIES_OUTCOME: ${{ steps.install_dependencies.outcome }} + STEPS_UPDATE_PROD_DEPS_OUTCOME: ${{ steps.update_prod_deps.outcome }} + STEPS_DEDUPE_DEPS_OUTCOME: ${{ steps.dedupe_deps.outcome }} + STEPS_BUILD_PACKAGES_OUTCOME: ${{ steps.build_packages.outcome }} + STEPS_RUN_UNIT_TESTS_OUTCOME: ${{ steps.run_unit_tests.outcome }} + 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_SETUP_VP_OUTCOME}" = "failure" ]; then + failed_step="Setup Vite+" + 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/pr-preview-cleanup.yml b/.github/workflows/pr-preview-cleanup.yml new file mode 100644 index 0000000000..239dc2f381 --- /dev/null +++ b/.github/workflows/pr-preview-cleanup.yml @@ -0,0 +1,30 @@ +name: pr-preview-cleanup + +# Removes the GitHub Pages e2e-report preview that `build.yml`'s `deploy-report` +# job published for a PR, once that PR is closed/merged. Kept separate from +# `build.yml` so closing a PR doesn't re-run the whole build + e2e suite. +on: + pull_request: + types: [closed] + +permissions: + contents: write + pull-requests: write + +jobs: + remove-preview: + name: "Remove E2E Report Preview" + runs-on: ubuntu-latest + # Fork PRs never got a preview (read-only token), so nothing to remove. + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Remove PR preview + uses: rossjrw/pr-preview-action@ffa7509e91a3ec8dfc2e5536c4d5c1acdf7a6de9 # v1 + with: + preview-branch: gh-pages + umbrella-dir: pr-preview + action: remove diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000000..b39deb9957 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,33 @@ +# ./.github/workflows/publish.yml +name: Publish + +on: + push: + tags: + - v*.*.* + - v*.*.*-* + workflow_dispatch: + inputs: + version: + description: "Version tag to publish (e.g., v0.x.x-hotfix)" + required: true + type: string + +permissions: {} + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Publish disabled + # NX has been removed (it was driving `nx release publish`) and the + # replacement release tool is not yet in place. This workflow is + # intentionally a no-op so a stray tag push doesn't silently succeed + # without actually publishing anything. Once a release tool is wired + # up, restore the publish steps and remove this guard. + run: | + echo "::error::Publish workflow is disabled — release tooling is being replaced." + echo "::error::Re-enable this workflow once a release tool is wired up." + exit 1 diff --git a/.github/workflows/relative-ci.yaml b/.github/workflows/relative-ci.yaml index c2b848e202..fdee2a57f5 100644 --- a/.github/workflows/relative-ci.yaml +++ b/.github/workflows/relative-ci.yaml @@ -1,17 +1,24 @@ name: RelativeCI on: + # zizmor: ignore[dangerous-triggers] -- workflow_run is the recommended pattern + # for RelativeCI; this workflow only downloads artifacts and reports bundle stats. workflow_run: workflows: ["build"] types: - completed +permissions: + actions: read + contents: read + jobs: build: runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' steps: - name: Send bundle stats and build information to RelativeCI (editor) - uses: relative-ci/agent-action@v2 + uses: relative-ci/agent-action@fcf45416581928e8dd62eded78ce98c78e5149f8 # v3.2.3 with: artifactName: relative-ci-artifacts-editor key: ${{ secrets.RELATIVE_CI_KEY }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..0b9ea34841 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,26 @@ +name: GitHub Actions Security Analysis with zizmor + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 diff --git a/.gitignore b/.gitignore index bc0541fef0..9fac8b5d4a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ examples/*/types tsconfig.tsbuildinfo # testing coverage +# Vitest's attachment store (failure screenshots / diffs). Accumulates across +# runs; the HTML reporter copies what it needs into playwright-report/data, +# so the originals are unreferenced cruft. +.vitest-attachments/ # production /build @@ -29,7 +33,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/.node-version b/.node-version new file mode 100644 index 0000000000..5bf4400f22 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.15.0 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index df9385826f..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v20.11.0 \ No newline at end of file diff --git a/.resources/release-workflow.excalidraw.svg b/.resources/release-workflow.excalidraw.svg new file mode 100644 index 0000000000..635f94e031 --- /dev/null +++ b/.resources/release-workflow.excalidraw.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1daVPjSNL+Pr+CYL+Oa+rIuiZiY4OzuZrmhu53N1xiYVx1MDAwYqzGV9sy18b8981cdTAwMTJgyTp8imNe8MTQINlyqSrzyefJyir997eFhcXwvuMv/rmw6N9VvUZQ63q3i7+74zd+t1x1MDAxN7RbeIpHf/fa/W41emc9XGY7vT//+KPpda/9sNPwqj65XHR6fa/RXHUwMDBi+7WgTart5lx1MDAxZkHoN3v/cj93vab/z067WVx1MDAwYrsk/pKKX1x1MDAwYsJ29/G7/Ibf9FthXHUwMDBmr/5/+PfCwn+jn3gmqLlvXFxcdTAwMGVXL+9cdTAwMWL74deNfT/o9b7p063aTvTR6E3Pt9D1q6HXumr48ak7134lidSGcTk4fI+HXHUwMDA1N1x1MDAwNFx1MDAxNFx1MDAxOFx1MDAxMDo+cVx1MDAxYtTCuvuMXHUwMDE1xFIuhk7W/eCqXHUwMDFl4lmlXHRcdTAwMDdFbfLk41x1MDAxN/+5QFx1MDAwN0d6Ybd97a+0XHUwMDFieJvYun8w3/1cdTAwMTe37cKrXl912/1WbfCesOu1elx1MDAxZK+LnVx1MDAxMb/vMmg0XHUwMDBlw/vo6jhcZth/i6nvOH1udep40afwS6/qLb/nOpxcco62O141XGJd1zBcdTAwMWHfhWthZ7NcdTAwMTaNzX/iNnVxVDfd4LT6jcbgcNCq+a7LXHUwMDE3PTr0ba3a07c9XHUwMDBmbDxq4unIX3Hbfd9dmFkrgWqgcVx1MDAxZsc2yZRIXHUwMDFm3W23XCL7xHHGQbVg4s9cdTAwMDW9VTSxMLrsJZqpXHUwMDFmj4Fr21ra/JImOGRhoX9cdTAwMTdcdTAwMGZMwkCPw35nacfQtt23ZziGrTNxfb44eN9fv+df9vHDv3rypiFXetXuxY538O3LxuaZTn3L8/d73W77dtLrNreXz+g9rVxcb8neRTXo0rsjXHUwMDAzJVxcV5/srS6F96dL/pa3bWti46Z9/TDZdZ9+i+2o36l5j+PCNFx1MDAwMOWWO7eKbbJcdTAwMTG0rtNG1mhXr+Oh/C3R4Fx1MDAxNGzkj0pcdTAwMDY2hlx1MDAwNjVCXGYhKaHYXHUwMDBlYYQ2lqo0coAl0qTPPENcdTAwMDfaXHUwMDFmocJapqzlzOThXHUwMDA3/4SNXCLYOMnHjaG3P1x1MDAwM1x1MDAwNDNUXHUwMDE5yIFcdTAwMDI8qU0hPlxihFx1MDAwNisk2FnwYYTpMsqYhSlMN7ZEZ4F4+1x1MDAxODibQbiwgueDu8RItlvhYfDgXHUwMDFhz+nQ0XWvXHUwMDE5NFxc18uhay01gqtWdEFstt9dTHZFXHUwMDE4YFxiXHUwMDFlvKFcdTAwMTnUasl4WcWLekHL725OXHUwMDEyeNvd4CpoeY2j0e33+mH7wO893kHY7fvJPvI3nr2CXHUwMDExLkd48/fdS6heVM9Pjyu/NpYqv1x1MDAwZU6aS/UpSFx1MDAwMFx1MDAwNnRuVdJcdTAwMWYjm5VcdTAwMWGPp+jBJ1x1MDAwYijJnfn8LEBcdTAwMWIhhTEmz8lcck9cdTAwMWZ8dnKjhaWG2pl8fDZcdTAwMGVw9KXe8S3oy/2ls/Pj/ZtcdTAwMDPYvDyeNKYurff36O6vk2D/y4p3sNLwf57c8Vx1MDAxMmL1V1x1MDAxYjZ3fj7Q70fy/PzI6zbY8a54W24xnlx1MDAwMyCOakWTIDE7XHUwMDA3yFx1MDAxZpVJOIAwRFx1MDAwYmqpUI5QJuwv8lxybVx0xaM8XHUwMDE3N1x1MDAxOFx1MDAwMOFcdTAwMTYpXHUwMDAwXHUwMDAzXHUwMDFi4UdcdTAwMWNcdTAwMTU+OcB40JiCXHUwMDAzIM1cdTAwMDfNKctDXHUwMDA3y9JcdTAwMDef0Vx1MDAwMYWFtpKVzlx1MDAwMDBcdTAwMTZIY6Yw3FwiXHUwMDA2sIznfS98XHUwMDBmXHUwMDE0YEzYLaBcdTAwMDCZXHUwMDFiKIdcdTAwMDNshNtnle+Vo4fK2t7xXHUwMDE19I6q99ss681+o1x1MDAxMXR6KVx1MDAwNqCFJFx1MDAwMrSSaTqPzI1cYqYgn81cdTAwMDMlSlxmu/nAh1liLCZwYqtZlX9cdTAwMTAnhkkjPy+K/Kjh0bcxXHUwMDE05Li24JmjsfxH2aUgKVwiXzz0d8N1OLqtmO3T41x1MDAwNvVbXHUwMDA3/uqVXHUwMDE3vLX8V8u6d3xQv+339/yHcK3XU3VcdTAwMWGUXHUwMDE1opnSIEtcbtH5vTdBiNZcdTAwMWFcYlx1MDAwMDdGXG5upUiJdCaIMVx1MDAxOISRn1x1MDAxYkM1y/q2YiQ6b2w6XHI48PDpqP1HcnA5eZDWXHUwMDE01bbUuTqdM1royZpKishLS1x1MDAxN+rTXHUwMDFhbyZMX+AwVuv/bv271cRQ+Vx1MDAxZcL0mMiYXHUwMDBl00U3UE6YXHUwMDFlXHJpKdhJXHUwMDA1aUHAglVI6UDLlFhHJUioUlYxtCipKI159SBeo86XiimrLXJ0XHUwMDEwSuVQb2KAXHUwMDAz8lx1MDAwMIH/akhEmE8mPuTjau4gjtpHOVx0n1x1MDAxZsRcdTAwMGJz+ILiXHUwMDEwKuRqpXu+YiCTNj2p53faQZpcIsS/LcRcdTAwMDZcdTAwMTP9Mfj9P7/nvrtSbKXulbHP+HqZyNzweuFKxLnxRvdcXCMzIFx1MDAxY3rdcFx1MDAxOVx1MDAwNzVoXVxyXHUwMDBm3tO02yRcdTAwMDBcdTAwMTKhWLXvOqDicuOGWsVd0Fx1MDAxNFx1MDAxNFisjZ2peZ1cYiCMYVx1MDAxMsUzuitHkWazXHUwMDE24rdq41s1Olx1MDAwMZlqXHUwMDE1clx1MDAwMNdcdTAwMWPgkmrBIdsoSlxmXHUwMDAyXHUwMDBiXHUwMDAztEeOtECorNm6zlpyuFT3vYxnYJOT59JcdTAwMDDmNy7at1x1MDAxM1FcdTAwMWVdu+xe337bkltcdTAwMWK791x1MDAwN3qn3vmxVp1Ux3BLXHUwMDE0gFx1MDAxNOlcdM1cblx1MDAxNyRvLrNEXHTDL61cdTAwMGbwQdBPz49+XHUwMDE0XHUwMDE0N0LlJi+FLYQ/TlGlXHUwMDFhIWfDv3xRME7DtE62WbtnVH/J7Jz/guPNze2mP6nWuLz+Unvo6dsl7V3IXHUwMDFlX1x1MDAxNvubbKk8rcE5jzngXFxaI/8uJ9FcdTAwMWFcdTAwMDbBQ1gtpGVcdTAwMTJ02vXwJEU/U1x1MDAwNlx1MDAwNavlKEgyXig00Sg2jNbpaYjZxMaHckUzudhAWMfxwciUJzaMLXI5JSmaXHUwMDE5XHUwMDA249JcdTAwMTnHlMab0Vx1MDAxYaF35Xj6XHIj9D1cYo0xoSstNHJbX47KWPd27+12pbtz5/HNL5WNm23a/zL5hKC0RDKbXHKjkqT88yVnXHUwMDAzP5RcdTAwMTcvzz9cdTAwMWLIUFx1MDAxM1xilkzmxN6tIH0wnvI3XFxcYmAyliEvXHUwMDFlT+XP7vH9WVx1MDAwNUT7un7g1Vx1MDAwM73SPdp4hXg68rpzTFx1MDAwN75unM7vvVx04rRwJFhQLTVcdTAwMDPGmFx1MDAxOJ6244pcdTAwMDBLl+XEqVx1MDAwM0W4tsYyq7TW88/afSznnqZyxzjcpTZcdTAwMWKOXHUwMDE3R1XuXHUwMDAwZVx1MDAwNj86U2L/RUP006TX0p9cdTAwMGJdbI+XVGxvXHUwMDE3qMeEx4KJu7x7KCdcXI/GtJFJQS5cdOpdKjTjXHUwMDFj0HmHfFx1MDAxYVxmXHUwMDExXGbAaODUXCJcdI8xfignaCXFXHUwMDEwgMBgOc1xbEZcdTAwMTQzyliM4C5LhG/+dPR8R1+dW1x1MDAxNlx1MDAwYq2NQefLzM+7XHUwMDBmQeHMXHUwMDFlXHUwMDAwXHUwMDE3XFxcdDZTXHUwMDEwf1x1MDAxMf9/gaRgrpk+ns5YaHzFTHAuLS04mu4vXGYl4LDFWmilXHUwMDE0XHUwMDE1IJmU2VxmXHUwMDFj+jE1SMRcdTAwMDTqM6Yo11x1MDAxOVx1MDAxM5koKzhcdTAwMWHaXHUwMDEyjaKEoa1cdTAwMDFcbneuXHUwMDA150pnWmRcYlxixTjqdjDcXHUwMDFh+mYpwdHUbFx1MDAxNDyiXHJcdTAwMTOlQOFNcoP2bGJ4cy6vgXAuJEZO5DVKmCzxoURcdTAwMWGlmZbSgkZ1k6iaXHUwMDFi4KNcdTAwMTHYUVZTXHUwMDFjY86YSlx1MDAwNOnPOZMheFxcm1x1MDAxYlx1MDAxZTUyI8CAlps0zFxin4HGQXTAMVx1MDAwNF3+nFx0wzF/c3gsNlL3ypjna2DjXHUwMDE0MIRQp1x1MDAxNVx1MDAxOGw8OqlAmMlio1wiij8uKZhcclx1MDAxNEdcdTAwMTdqXHIjNZWMcqDKWoWkSiqWaVxyI+yxJuKt8HB0heloPFx1MDAwNFwiJJfCoIrjianHSFx1MDAwMaJcdTAwMDTEIIV3XHJUuLiQzdQyXCKBSoxcdTAwMWFWMyOtkjnZXHUwMDFla13ClzlTRKlJ2VRlXCJcdTAwMWZcdFx1MDAwZddLmERcdTAwMTGSMitEXHUwMDFlXWS8MOnDKXpcdTAwMWNcYlM6IFJXYjbNXHUwMDEypZfii4V26l5cdTAwMTlcdTAwMGJ9XHJEnFx1MDAxOINcdTAwMTCCkOQqI5VBRYeoLU3iXc+AXHUwMDE45WlmQ8OpJo6VdbzaLfYyTFKdZYmaPNd6v1x1MDAxNVx1MDAxY95cdTAwMDS97dOf2lx1MDAwYq9XjsTlSXDZuvxxMElCjJHMSrVcYlx1MDAwNylBtlx1MDAwYrmJbqZcdTAwMTWRSmPkN9rV4sdwMsA/8ZlcdTAwMDcrXHUwMDAwvK1p0mBOL1mRW1x1MDAxOKdcbte2oFx1MDAxNTKOjv1cdTAwMGVcdTAwMTNhO1xiJFx1MDAwYlx1MDAwN6NyYGbo6LhcdTAwMWNYw79M2EMmXHUwMDAzXHUwMDE2tjtF6a+hVqdzXfnNLCfN5Ymzr/T8q9e/Ovy5utbZOPxS9/cncVSuXHRqY1x1MDAxY1x1MDAwMCSHXHUwMDE23GTykMNKalx0itjc1eouc410xrpqm+Ey9tlcdTAwMWP2Q1x1MDAxNbJuT+6wnFx1MDAxYc2NhuzKkmhcdTAwMTCKl5wyytySYPH+Kll3/du/gcPmtrJcdTAwMWN/vTlXW3dqr3/X037zgVx1MDAxZepcdTAwMTV/+evks8hcbpClXGbP+kZJaSuJXHUwMDEw9lx1MDAxNTeX+EguuzOpqCicSEZSTKnkuTlcdTAwMTam0lx1MDAwN1x1MDAwN35MXWJcdTAwMTaUeMV55J/7XHUwMDA3nZ1qe6m7ut9onVx1MDAxZv+4P6vUq6+wXHUwMDA2ZOR1w29cdTAwMWR2dlC/XFzV9zW//sP7erZ5XFwv4bp/m60l8kdlgigvuELizZFqoSyTKNuGk7WSYSR/zlx1MDAwM+XEeUOEtFx1MDAwNrHBPuqRLHxMt670Q6HGNDPUyKOAS23zXG5NxIi9JThQK3X5zHzeOP80vbv6rqaox8TeginqvHsoh1x1MDAwYoyGy5FT1JJcdTAwMTO3q0h6wVxu0oBC0m6AXHUwMDE0UFx1MDAwMMZcclEowIfU+ac3XHUwMDBmefPu3IlFhmCrtVx1MDAwM+M8XHUwMDEyIFxu9TdnQii3XHUwMDA0XCImXHUwMDBmL7/CdP2wfb6lwffs/b7mXHUwMDFk7/5qlU5cdTAwMTf9hEJ9OFx1MDAwYoSUnLWkXHUwMDA2de4wM3YvIYlcdTAwMDWbzPiNuEbaddwr6TTxXHUwMDE1Mlx1MDAxZPMmXHUwMDBiZlx1MDAwNFNSaC2pKy/TYLKzP4IwbZGRamDSWFfmkjHbifKeoyF1IZmBNdggN02iolx1MDAxY5/OWcRDXHQ8yZSsXHUwMDBmvU7aM9/uJ+BZ0jCCNyVcdTAwMTWzkiojhpMpXHUwMDAwmjCqoaBcdTAwMTDQ7Vx0xKzRSFx1MDAxOI1bOfPJsqbA5al28Fx1MDAwMldcYpQrwlxuOVx1MDAxNrdcdTAwMTJcZpdlp1K4S+zMtXtXp9VpLnT7rffEsMYwmjTDKr6Fclxi1sP99mazXt168E63Tpa+Nk7XvtR+ZN25YP9cdTAwMGVcdTAwMDR9V1x1MDAwMpDewlx1MDAwYoVcdTAwMTSRqKWMNoDjmKhV+NzE4+nojL78bX6ORVx1MDAxNWhcdTAwMGL523dBoufTQsoq9Eg3zfF6XHUwMDFji+uTQJ1snV57u5tnUKtcdTAwMWZddZbEW2dExIlpVi+WvodVXHUwMDA2P07Ob46vT/SE11x1MDAxZM9cdJGWKFVSxX5+701cdTAwMTCptdFEgVs15+pcdTAwMDZcdTAwMTVNVfdaQ5hbQUzR8bRgXCJb3itcdTAwMDRB2mc1Uqdoy82c6rVPXHUwMDE5VeTie5NHa+DalS7JPLkkstv0XGbW8lx1MDAwM45cZv5fdk5kauPNXHUwMDA07HhtXHUwMDFhe1x1MDAwZrF6THAsXlnHylx1MDAwZdOjwWxsqb7RVKCZXGIuXHUwMDEyXCI66jhgXHUwMDA0LIpcctRB2iiet31cdTAwMDcl0lx1MDAxNfcpVNnozDQ2q8T2XHUwMDFkkqJcXFx1MDAxMUjqmUJVNVWp/ody7v354zdDdoVcdTAwMWFI5K3UUYXh2zDn8Xq2PbhexOfLLtUvMtPodNZC4ytmonJpXHSJ0fixMJSQQMUv0FxypVsxIy3PXHUwMDE2X1mCXHUwMDExXHUwMDE5vdRKN29NXz5cdTAwMWLBOZduR2fKjEDZXHUwMDBlKtMkTSj25Fx1MDAxYtZgjZ6dXHUwMDFhV6JPucJ709zV/FxmU1x1MDAxY4rGpPAnSlx1MDAxYorGkqNfkOBQXHUwMDE3RSlV2khGczY1spHE0VZcYlx1MDAwNcJtW/5cdIr5oHgwNyhq5VxmXHUwMDE1VFx1MDAxZVx1MDAxMSpevmQ1cFx1MDAwZbb03cyQlTH79phYbKPulbHO11x1MDAwMMSJa0ApcfP6hlwiXluqkbtAYnHBXHUwMDEz+lx1MDAwMDFSRJWikY9SeGFEdHWpIJFKcW4lgp6UOShccoSBoCCwOW7ZMnsrZNyonO38Wl+j3V5Xra3/4Fx1MDAxN/fNu7MsMtZcdTAwMDKv2W7VhrFcdTAwMTFjXGbqP7fVXHUwMDE1MHBUY5gyMvfwXHUwMDE2St1uysKF3Cw4gibGuM3gLLjiXGaVt7pcdTAwMTNcZuHKXG5AhVx0LsjEePSJjkPoeFhcdTAwMDJldMFcZiBXJ1x1MDAxNlNGXHUwMDA2yKUw+s+Gj4PmTZXxXHTv+M/L83brst7cqNfMr93m2faPV8jMjLxuuLZ1uVFdXtvX4nD7tn5cdTAwMTP0b+4vS8v4XGJjbVx1MDAxY6Lmyvjk917G6XNK0im4qnSJ/uxSNql0rtSaMKFBXHUwMDAw+rw0POvxXFxcdTAwMTBA7p0uX/+cnFx1MDAxOevdR1PMzXDGrCyYXHUwMDFkL66QXHUwMDEzhmm3XHUwMDE1VtnKj1smXHUwMDEzXHUwMDBisWbI9qxsvockz5hImU7yJFx1MDAxYl1ObuebZUtcdTAwMWLfVi5Pu0bX91x1MDAxYms3P+8qtazX5k7BME5cdTAwMTlcdTAwMDHmNltWXHUwMDFhhzlVvKasJNYghUJZyalKTOFccmI1M8hcdTAwMDO1dNXtbvo1sUtXXHUwMDFjq5lcIlx1MDAxY1x1MDAxOY1cdTAwMDFcdTAwMTDKcPPpzUXefDx3rObI1Fx1MDAxMWJ1bpXbiFx1MDAwNSiKuqWTiZ1UXzxUr8C328PW8ZatLCn4Zfs/tzf3vf93ITX/LidcdKlcXFiCXHUwMDAzSfFfit5ph1NcZlx1MDAxYVx1MDAwM66k6JpWI8tiOXuh41x1MDAxYlBAINy7x1jJ3CUkn45Y5IhTlDxwiUFVstx8amHqgEfyx8jSo6rbU0fMXHUwMDE1VXf3vr6HsDomqmXWkyRbXU5cXFx1MDAxZC1cdTAwMDdGJVx1MDAwN3FcXIlcdTAwMTbouyApQitcZlx1MDAwN1VUtYRS4Z5zXHUwMDA3Qlids72Rta4qXFwpYcE9/ILnZFx1MDAwN1xy6l/U2FxiXHUwMDAxWridV+LveNeuXFxru1x1MDAxY9Cr+vLp/Fx1MDAwMljgMLD8RWJcIltcYpGgzmCVYOZcdTAwMTVcdTAwMWZcXLJ5u37Uu9r7Xv1F61x1MDAwZqZ3fPv94JxNXHUwMDE5/VDDzfTIo3Kzj1RcdTAwMTOtmNJcdTAwMTj8XHUwMDEwXWGoMrRcIlFuXCLoaoxtxlAux1aYXHUwMDE2u5R7XHRFXHUwMDA0XHUwMDE4hTRYgfuyV0lmTjW749JhXHUwMDFhXHUwMDFixiW4jHPOVuhcdTAwMTnjnSh7OVq9LCQzqm41vWTcXGKDI5JT8GqJ5MxYhYTF9fDb7cWe71x1MDAwM1x1MDAxM3AuS1x1MDAwNbFcdTAwMTI7mUpwe1rFIT1cbnTCZZVR5Vx1MDAxOM0szYFtraJccqFcdTAwMDE1lVKOc/19XHUwMDE511x1MDAxYsD02eSUXHUwMDBikYpGWd88lVO4ITTjXHUwMDBlT6icbc/YkXPYruh6XHUwMDFlztXyb1x1MDAxN0Lv6j3wrjGsJ827Mi0vqV5lpL5cdTAwMWLFvVx1MDAxOHVL6txT4YREL5R2OFx1MDAxN6lcdTAwMTRGXHUwMDBldF6FtExTw2JjXHUwMDFlkC8hSbRcdTAwMTRPcmO0wlxinnVjq92us04lKiO50vFb3rVXv76O+j4/90I0NkIolffAmeJcdTAwMDcuMOv2pJR0ti01ZuNeO+c77crh3sny7nrb2/zZv12p7+5Nx73chmJvv6RcdTAwMDc4oShf0EWYckWcdogsaeN2XHUwMDFiM1x1MDAxOFx1MDAwNjXHXHUwMDFmg1xyXHUwMDE2i7lXoUe5V8aXXoN7TUF7XHUwMDA0Ui5rqEUxh4pcdTAwMWRsdlWNdeTRlaK7XHUwMDE1plrZ563qpqRio1x1MDAxNe8wXHUwMDFm5EjCXHUwMDEwyoybmrdKZtvEidRIXHUwMDE1XHUwMDE109Hz8CC7L+crkbF8p5iAjDHmdn2zeFx1MDAwN247KVx1MDAwZTC84Fx1MDAwN7uZuH27XHUwMDFjOlx1MDAxOKNcdTAwMTJPXHUwMDA1XHUwMDFiPFxmXHUwMDEwP4537yaiU89cYv3b0bHXXHUwMDA37lx1MDAxZlOwMbR7p8KyO1mOYmNu+y0+VNVUXHUwMDEyXHUwMDE5XHUwMDAzMHYuMtbpXzSCXv09kLExNCiz2Cfd8qnJ2G9PaLvodTqHIfbsXHUwMDAwr9CQglqqXHUwMDE3XHUwMDFlj4V+J+6B6NDXds1fa3lcdTAwMTeNdC8v3lx1MDAwNP7tctZv/nFcdTAwMTm9XFysjFDE+YxcdTAwMWZcdTAwMTGFv37763+eXHJeiiJ9commit C: fixcommit B: featbranchmaintagv1.0commit A: releaseLast ReleaseNew Releasecommit D: releasepnpm run releasetagv1.1CINPMnew tagpublish \ No newline at end of file diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit new file mode 100755 index 0000000000..85fb65b4fc --- /dev/null +++ b/.vite-hooks/pre-commit @@ -0,0 +1 @@ +vp staged diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c224675018..1b6c379e0a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,5 @@ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format - "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] + "recommendations": ["VoidZero.vite-plus-extension-pack"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 24d66a74e9..80f6014497 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,25 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.formatOnSave": true, - "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.formatOnSaveMode": "file", + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "explicit" }, + "oxc.fmt.configPath": "./vite.config.ts", + "npm.scriptRunner": "vp", + "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[javascriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, "search.exclude": { "packages/editor/public/types": true, "packages/website/docs/.vitepress": false, "**/.*": false }, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "[xml]": { + "editor.defaultFormatter": "redhat.vscode-xml" + }, + "scm.defaultViewMode": "tree", + "search.defaultViewMode": "tree" } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..362b82a43a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ + + +# Using Vite+, the Unified Toolchain for the Web + +This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, and it invokes Vite through `vp dev` and `vp build`. Run `vp help` to print a list of commands and `vp --help` for information about a specific command. + +Docs are local at `node_modules/vite-plus/docs` or online at https://viteplus.dev/guide/. + +## Review Checklist + +- [ ] Run `vp install` after pulling remote changes and before getting started. +- [ ] Run `vp check` and `vp test` to format, lint, type check and test changes. +- [ ] Check if there are `vite.config.ts` tasks or `package.json` scripts necessary for validation, run via `vp run Basic Setup +
diff --git a/examples/01-basic/01-minimal/main.tsx b/examples/01-basic/01-minimal/main.tsx index f88b490fbd..1260513388 100644 --- a/examples/01-basic/01-minimal/main.tsx +++ b/examples/01-basic/01-minimal/main.tsx @@ -1,11 +1,11 @@ // AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY import React from "react"; import { createRoot } from "react-dom/client"; -import App from "./App"; +import App from "./src/App.jsx"; const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json index 5b64a77661..4638cbac21 100644 --- a/examples/01-basic/01-minimal/package.json +++ b/examples/01-basic/01-minimal/package.json @@ -1,37 +1,30 @@ { - "name": "@blocknote/example-minimal", + "name": "@blocknote/example-basic-minimal", "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", "@blocknote/mantine": "latest", + "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" }, "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/01-minimal/src/App.tsx b/examples/01-basic/01-minimal/src/App.tsx new file mode 100644 index 0000000000..a3b92bafd2 --- /dev/null +++ b/examples/01-basic/01-minimal/src/App.tsx @@ -0,0 +1,12 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote(); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/01-minimal/tsconfig.json b/examples/01-basic/01-minimal/tsconfig.json index 1bd8ab3c57..93fa81bee8 100644 --- a/examples/01-basic/01-minimal/tsconfig.json +++ b/examples/01-basic/01-minimal/tsconfig.json @@ -3,28 +3,21 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/01-basic/01-minimal/vite.config.ts b/examples/01-basic/01-minimal/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/01-basic/01-minimal/vite.config.ts +++ b/examples/01-basic/01-minimal/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/01-basic/02-block-objects/App.tsx b/examples/01-basic/02-block-objects/App.tsx deleted file mode 100644 index 846df5d180..0000000000 --- a/examples/01-basic/02-block-objects/App.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Block } from "@blocknote/core"; -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useState } from "react"; - -import "./styles.css"; - -export default function App() { - // Stores the document JSON. - const [blocks, setBlocks] = useState([]); - - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "heading", - content: "This is a heading block", - }, - { - type: "paragraph", - content: "This is a paragraph block", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance and its document JSON. - return ( -
-
BlockNote Editor:
-
- { - // Saves the document JSON to state. - setBlocks(editor.document); - }} - /> -
-
Document JSON:
-
-
-          {JSON.stringify(blocks, null, 2)}
-        
-
-
- ); -} diff --git a/examples/01-basic/02-block-objects/README.md b/examples/01-basic/02-block-objects/README.md index d7c26a41e4..18e9120493 100644 --- a/examples/01-basic/02-block-objects/README.md +++ b/examples/01-basic/02-block-objects/README.md @@ -6,5 +6,5 @@ In this example, the document's JSON representation is displayed below the edito **Relevant Docs:** -- [Document Structure](/docs/editor-basics/document-structure) -- [Getting the Document](/docs/editor-api/manipulating-blocks#getting-the-document) +- [Document Structure](/docs/foundations/document-structure) +- [Getting the Document](/docs/reference/editor/manipulating-content) diff --git a/examples/01-basic/02-block-objects/index.html b/examples/01-basic/02-block-objects/index.html index ec99551235..7dbba2a212 100644 --- a/examples/01-basic/02-block-objects/index.html +++ b/examples/01-basic/02-block-objects/index.html @@ -1,11 +1,11 @@ - Displaying Document JSON +
diff --git a/examples/01-basic/02-block-objects/main.tsx b/examples/01-basic/02-block-objects/main.tsx index f88b490fbd..1260513388 100644 --- a/examples/01-basic/02-block-objects/main.tsx +++ b/examples/01-basic/02-block-objects/main.tsx @@ -1,11 +1,11 @@ // AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY import React from "react"; import { createRoot } from "react-dom/client"; -import App from "./App"; +import App from "./src/App.jsx"; const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/01-basic/02-block-objects/package.json b/examples/01-basic/02-block-objects/package.json index 2b0cb24707..2e98b6e6ab 100644 --- a/examples/01-basic/02-block-objects/package.json +++ b/examples/01-basic/02-block-objects/package.json @@ -1,37 +1,30 @@ { - "name": "@blocknote/example-block-objects", + "name": "@blocknote/example-basic-block-objects", "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", "@blocknote/mantine": "latest", + "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" }, "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/02-block-objects/src/App.tsx b/examples/01-basic/02-block-objects/src/App.tsx new file mode 100644 index 0000000000..c3d623f2e8 --- /dev/null +++ b/examples/01-basic/02-block-objects/src/App.tsx @@ -0,0 +1,56 @@ +import { Block } from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { useEffect, useState } from "react"; + +import "./styles.css"; + +export default function App() { + // Stores the document JSON. + const [blocks, setBlocks] = useState([]); + + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "heading", + content: "This is a heading block", + }, + { + type: "paragraph", + content: "This is a paragraph block", + }, + ], + }); + + // Sets the initial document JSON + useEffect(() => setBlocks(editor.document), []); + + // Renders the editor instance and its document JSON. + return ( +
+
BlockNote Editor:
+
+ { + // Sets the document JSON whenever the editor content changes. + setBlocks(editor.document); + }} + /> +
+
Document JSON:
+
+
+          {JSON.stringify(blocks, null, 2)}
+        
+
+
+ ); +} diff --git a/examples/01-basic/02-block-objects/src/styles.css b/examples/01-basic/02-block-objects/src/styles.css new file mode 100644 index 0000000000..6d5eeba7fe --- /dev/null +++ b/examples/01-basic/02-block-objects/src/styles.css @@ -0,0 +1,25 @@ +.wrapper { + display: flex; + flex-direction: column; + height: 100%; +} + +.item { + border-radius: 0.5rem; + flex: 1; + overflow: hidden; +} + +.item.bordered { + border: 1px solid gray; +} + +.item pre { + border-radius: 0.5rem; + height: 100%; + overflow: auto; + padding-block: 1rem; + padding-inline: 54px; + width: 100%; + white-space: pre-wrap; +} diff --git a/examples/01-basic/02-block-objects/styles.css b/examples/01-basic/02-block-objects/styles.css deleted file mode 100644 index 89d965109f..0000000000 --- a/examples/01-basic/02-block-objects/styles.css +++ /dev/null @@ -1,25 +0,0 @@ -.wrapper { - display: flex; - flex-direction: column; - height: 100%; -} - -.item { - border-radius: 0.5rem; - flex: 1; - overflow: hidden; -} - -.item.bordered { - border: 1px solid gray; -} - -.item pre { - border-radius: 0.5rem; - height: 100%; - overflow: auto; - padding-block: 1rem; - padding-inline: 54px; - width: 100%; - white-space: pre-wrap; -} \ No newline at end of file diff --git a/examples/01-basic/02-block-objects/tsconfig.json b/examples/01-basic/02-block-objects/tsconfig.json index 1bd8ab3c57..93fa81bee8 100644 --- a/examples/01-basic/02-block-objects/tsconfig.json +++ b/examples/01-basic/02-block-objects/tsconfig.json @@ -3,28 +3,21 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/01-basic/02-block-objects/vite.config.ts b/examples/01-basic/02-block-objects/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/01-basic/02-block-objects/vite.config.ts +++ b/examples/01-basic/02-block-objects/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/01-basic/03-all-blocks/App.tsx b/examples/01-basic/03-all-blocks/App.tsx deleted file mode 100644 index 2fe5509438..0000000000 --- a/examples/01-basic/03-all-blocks/App.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - }, - { - type: "paragraph", - content: [ - { - type: "text", - text: "Blocks:", - styles: { bold: true }, - }, - ], - }, - { - type: "paragraph", - content: "Paragraph", - }, - { - type: "heading", - content: "Heading", - }, - { - type: "bulletListItem", - content: "Bullet List Item", - }, - { - type: "numberedListItem", - content: "Numbered List Item", - }, - { - type: "checkListItem", - content: "Check List Item", - }, - { - type: "table", - content: { - type: "tableContent", - rows: [ - { - cells: ["Table Cell", "Table Cell", "Table Cell"], - }, - { - cells: ["Table Cell", "Table Cell", "Table Cell"], - }, - { - cells: ["Table Cell", "Table Cell", "Table Cell"], - }, - ], - }, - }, - { - type: "file", - }, - { - type: "image", - props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", - }, - }, - { - type: "video", - props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", - }, - }, - { - type: "audio", - props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", - caption: - "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", - }, - }, - { - type: "paragraph", - }, - { - type: "paragraph", - content: [ - { - type: "text", - text: "Inline Content:", - styles: { bold: true }, - }, - ], - }, - { - type: "paragraph", - content: [ - { - type: "text", - text: "Styled Text", - styles: { - bold: true, - italic: true, - textColor: "red", - backgroundColor: "blue", - }, - }, - { - type: "text", - text: " ", - styles: {}, - }, - { - type: "link", - content: "Link", - href: "https://www.blocknotejs.org", - }, - ], - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance using a React component. - return ; -} diff --git a/examples/01-basic/03-all-blocks/README.md b/examples/01-basic/03-all-blocks/README.md deleted file mode 100644 index d8b2480991..0000000000 --- a/examples/01-basic/03-all-blocks/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Default Schema Showcase - -This example showcases each block and inline content type in BlockNote's default schema. - -**Relevant Docs:** - -- [Document Structure](/docs/editor-basics/document-structure) -- [Default Schema](/docs/editor-basics/default-schema) diff --git a/examples/01-basic/03-all-blocks/index.html b/examples/01-basic/03-all-blocks/index.html deleted file mode 100644 index 18c0226a18..0000000000 --- a/examples/01-basic/03-all-blocks/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Default Schema Showcase - - -
- - - diff --git a/examples/01-basic/03-all-blocks/main.tsx b/examples/01-basic/03-all-blocks/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/03-all-blocks/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/03-all-blocks/package.json b/examples/01-basic/03-all-blocks/package.json deleted file mode 100644 index 7502ea135e..0000000000 --- a/examples/01-basic/03-all-blocks/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-all-blocks", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/03-all-blocks/tsconfig.json b/examples/01-basic/03-all-blocks/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/03-all-blocks/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/03-all-blocks/vite.config.ts b/examples/01-basic/03-all-blocks/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/03-all-blocks/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/03-multi-column/.bnexample.json b/examples/01-basic/03-multi-column/.bnexample.json new file mode 100644 index 0000000000..19dc65d063 --- /dev/null +++ b/examples/01-basic/03-multi-column/.bnexample.json @@ -0,0 +1,10 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": ["Basic", "Blocks"], + "dependencies": { + "@blocknote/xl-multi-column": "latest" + }, + "pro": true +} diff --git a/examples/01-basic/03-multi-column/README.md b/examples/01-basic/03-multi-column/README.md new file mode 100644 index 0000000000..6c2e663d21 --- /dev/null +++ b/examples/01-basic/03-multi-column/README.md @@ -0,0 +1,8 @@ +# Multi-Column Blocks + +This example showcases multi-column blocks, allowing you to stack blocks next to each other. These come as part of the `@blocknote/xl-multi-column` package. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Document Structure](/docs/foundations/document-structure) diff --git a/examples/01-basic/03-multi-column/index.html b/examples/01-basic/03-multi-column/index.html new file mode 100644 index 0000000000..914212d28f --- /dev/null +++ b/examples/01-basic/03-multi-column/index.html @@ -0,0 +1,14 @@ + + + + + Multi-Column Blocks + + + +
+ + + diff --git a/examples/01-basic/03-multi-column/main.tsx b/examples/01-basic/03-multi-column/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/03-multi-column/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/03-multi-column/package.json b/examples/01-basic/03-multi-column/package.json new file mode 100644 index 0000000000..96d12ed3ce --- /dev/null +++ b/examples/01-basic/03-multi-column/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-basic-multi-column", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@blocknote/xl-multi-column": "latest" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/03-multi-column/src/App.tsx b/examples/01-basic/03-multi-column/src/App.tsx new file mode 100644 index 0000000000..d135323281 --- /dev/null +++ b/examples/01-basic/03-multi-column/src/App.tsx @@ -0,0 +1,116 @@ +import { BlockNoteSchema, combineByGroup } from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import * as locales from "@blocknote/core/locales"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + SuggestionMenuController, + getDefaultReactSlashMenuItems, + useCreateBlockNote, +} from "@blocknote/react"; +import { + getMultiColumnSlashMenuItems, + multiColumnDropCursor, + locales as multiColumnLocales, + withMultiColumn, +} from "@blocknote/xl-multi-column"; +import { useMemo } from "react"; +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // Adds column and column list blocks to the schema. + schema: withMultiColumn(BlockNoteSchema.create()), + // The default drop cursor only shows up above and below blocks - we replace + // it with the multi-column one that also shows up on the sides of blocks. + dropCursor: multiColumnDropCursor, + // Merges the default dictionary with the multi-column dictionary. + dictionary: { + ...locales.en, + multi_column: multiColumnLocales.en, + }, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "columnList", + children: [ + { + type: "column", + props: { + width: 0.8, + }, + children: [ + { + type: "paragraph", + content: "This paragraph is in a column!", + }, + ], + }, + { + type: "column", + props: { + width: 1.4, + }, + children: [ + { + type: "heading", + content: "So is this heading!", + }, + ], + }, + { + type: "column", + props: { + width: 0.8, + }, + children: [ + { + type: "paragraph", + content: "You can have multiple blocks in a column too", + }, + { + type: "bulletListItem", + content: "Block 1", + }, + { + type: "bulletListItem", + content: "Block 2", + }, + { + type: "bulletListItem", + content: "Block 3", + }, + ], + }, + ], + }, + ], + }); + + // Gets the default slash menu items merged with the multi-column ones. + const getSlashMenuItems = useMemo(() => { + return async (query: string) => + filterSuggestionItems( + combineByGroup( + getDefaultReactSlashMenuItems(editor), + getMultiColumnSlashMenuItems(editor), + ), + query, + ); + }, [editor]); + + // Renders the editor instance using a React component. + return ( + + {/* Replaces the default slash menu with one that has both the default + items and the multi-column ones. */} + + + ); +} diff --git a/examples/01-basic/03-multi-column/tsconfig.json b/examples/01-basic/03-multi-column/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/03-multi-column/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/03-multi-column/vite.config.ts b/examples/01-basic/03-multi-column/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/03-multi-column/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/03-all-blocks/.bnexample.json b/examples/01-basic/04-default-blocks/.bnexample.json similarity index 100% rename from examples/01-basic/03-all-blocks/.bnexample.json rename to examples/01-basic/04-default-blocks/.bnexample.json diff --git a/examples/01-basic/04-default-blocks/README.md b/examples/01-basic/04-default-blocks/README.md new file mode 100644 index 0000000000..2bb4b609f7 --- /dev/null +++ b/examples/01-basic/04-default-blocks/README.md @@ -0,0 +1,9 @@ +# Default Schema Showcase + +This example showcases each block and inline content type in BlockNote's default schema. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Document Structure](/docs/foundations/document-structure) +- [Default Schema](/docs/foundations/schemas) diff --git a/examples/01-basic/04-default-blocks/index.html b/examples/01-basic/04-default-blocks/index.html new file mode 100644 index 0000000000..e3e4e92661 --- /dev/null +++ b/examples/01-basic/04-default-blocks/index.html @@ -0,0 +1,14 @@ + + + + + Default Schema Showcase + + + +
+ + + diff --git a/examples/01-basic/04-default-blocks/main.tsx b/examples/01-basic/04-default-blocks/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/04-default-blocks/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/04-default-blocks/package.json b/examples/01-basic/04-default-blocks/package.json new file mode 100644 index 0000000000..e35609da3f --- /dev/null +++ b/examples/01-basic/04-default-blocks/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-default-blocks", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/04-default-blocks/src/App.tsx b/examples/01-basic/04-default-blocks/src/App.tsx new file mode 100644 index 0000000000..0d55d1af3d --- /dev/null +++ b/examples/01-basic/04-default-blocks/src/App.tsx @@ -0,0 +1,153 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Blocks:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: "Paragraph", + }, + { + type: "heading", + content: "Heading", + }, + { + id: "toggle-heading", + type: "heading", + props: { isToggleable: true }, + content: "Toggle Heading", + }, + { + type: "quote", + content: "Quote", + }, + { + type: "bulletListItem", + content: "Bullet List Item", + }, + { + type: "numberedListItem", + content: "Numbered List Item", + }, + { + type: "checkListItem", + content: "Check List Item", + }, + { + id: "toggle-list-item", + type: "toggleListItem", + content: "Toggle List Item", + }, + { + type: "codeBlock", + props: { language: "javascript" }, + content: "console.log('Hello, world!');", + }, + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Table Cell", "Table Cell", "Table Cell"], + }, + { + cells: ["Table Cell", "Table Cell", "Table Cell"], + }, + { + cells: ["Table Cell", "Table Cell", "Table Cell"], + }, + ], + }, + }, + { + type: "file", + }, + { + type: "image", + props: { + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", + }, + }, + { + type: "video", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + }, + }, + { + type: "audio", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + }, + }, + { + type: "paragraph", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline Content:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Styled Text", + styles: { + bold: true, + italic: true, + textColor: "red", + backgroundColor: "blue", + }, + }, + { + type: "text", + text: " ", + styles: {}, + }, + { + type: "link", + content: "Link", + href: "https://www.blocknotejs.org", + }, + ], + }, + ], + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/04-default-blocks/tsconfig.json b/examples/01-basic/04-default-blocks/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/04-default-blocks/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/04-default-blocks/vite.config.ts b/examples/01-basic/04-default-blocks/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/04-default-blocks/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/04-selection-blocks/App.tsx b/examples/01-basic/04-selection-blocks/App.tsx deleted file mode 100644 index 5251754f5a..0000000000 --- a/examples/01-basic/04-selection-blocks/App.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Block } from "@blocknote/core"; -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useState } from "react"; - -import "./styles.css"; - -export default function App() { - // Stores the selected blocks as an array of Block objects. - const [blocks, setBlocks] = useState([]); - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Select different blocks to see the JSON change below", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( -
-
BlockNote Editor:
-
- { - const selection = editor.getSelection(); - - // Get the blocks in the current selection and store on the state. If - // the selection is empty, store the block containing the text cursor - // instead. - if (selection !== undefined) { - setBlocks(selection.blocks); - } else { - setBlocks([editor.getTextCursorPosition().block]); - } - }} - /> -
-
Selection JSON:
-
-
-          {JSON.stringify(blocks, null, 2)}
-        
-
-
- ); -} diff --git a/examples/01-basic/04-selection-blocks/README.md b/examples/01-basic/04-selection-blocks/README.md deleted file mode 100644 index c85766c2e8..0000000000 --- a/examples/01-basic/04-selection-blocks/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Displaying Selected Blocks - -In this example, the JSON representation of blocks spanned by the user's selection, is displayed below the editor. - -**Try it out:** Select different blocks in the editor and see the JSON update! - -**Relevant Docs:** - -- [Cursor & Selections](/docs/editor-api/cursor-selections) diff --git a/examples/01-basic/04-selection-blocks/index.html b/examples/01-basic/04-selection-blocks/index.html deleted file mode 100644 index 3821ca8014..0000000000 --- a/examples/01-basic/04-selection-blocks/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Displaying Selected Blocks - - -
- - - diff --git a/examples/01-basic/04-selection-blocks/main.tsx b/examples/01-basic/04-selection-blocks/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/04-selection-blocks/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/04-selection-blocks/package.json b/examples/01-basic/04-selection-blocks/package.json deleted file mode 100644 index ba8f2a6632..0000000000 --- a/examples/01-basic/04-selection-blocks/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-selection-blocks", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/04-selection-blocks/styles.css b/examples/01-basic/04-selection-blocks/styles.css deleted file mode 100644 index 89d965109f..0000000000 --- a/examples/01-basic/04-selection-blocks/styles.css +++ /dev/null @@ -1,25 +0,0 @@ -.wrapper { - display: flex; - flex-direction: column; - height: 100%; -} - -.item { - border-radius: 0.5rem; - flex: 1; - overflow: hidden; -} - -.item.bordered { - border: 1px solid gray; -} - -.item pre { - border-radius: 0.5rem; - height: 100%; - overflow: auto; - padding-block: 1rem; - padding-inline: 54px; - width: 100%; - white-space: pre-wrap; -} \ No newline at end of file diff --git a/examples/01-basic/04-selection-blocks/tsconfig.json b/examples/01-basic/04-selection-blocks/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/04-selection-blocks/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/04-selection-blocks/vite.config.ts b/examples/01-basic/04-selection-blocks/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/04-selection-blocks/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/05-block-manipulation/App.tsx b/examples/01-basic/05-block-manipulation/App.tsx deleted file mode 100644 index 412f39c56e..0000000000 --- a/examples/01-basic/05-block-manipulation/App.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import "./styles.css"; - -export default function App() { - const editor = useCreateBlockNote(); - - return ( -
- -
- {/*Inserts a new block at start of document.*/} - - {/*Updates the first block*/} - - {/*Removes the first block*/} - - {/*Replaces the first block*/} - -
-
- ); -} diff --git a/examples/01-basic/05-block-manipulation/README.md b/examples/01-basic/05-block-manipulation/README.md deleted file mode 100644 index 9d1443176b..0000000000 --- a/examples/01-basic/05-block-manipulation/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Manipulating Blocks - -This example shows 4 buttons to manipulate the first block using the `insertBlocks`, `updateBlock`, `removeBlocks` and `replaceBlocks` methods. - -**Relevant Docs:** - -- [Block Manipulation](/docs/manipulating-blocks) diff --git a/examples/01-basic/05-block-manipulation/index.html b/examples/01-basic/05-block-manipulation/index.html deleted file mode 100644 index 44812420e7..0000000000 --- a/examples/01-basic/05-block-manipulation/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Manipulating Blocks - - -
- - - diff --git a/examples/01-basic/05-block-manipulation/main.tsx b/examples/01-basic/05-block-manipulation/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/05-block-manipulation/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/05-block-manipulation/package.json b/examples/01-basic/05-block-manipulation/package.json deleted file mode 100644 index 842db66875..0000000000 --- a/examples/01-basic/05-block-manipulation/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-block-manipulation", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/05-block-manipulation/styles.css b/examples/01-basic/05-block-manipulation/styles.css deleted file mode 100644 index eaade15302..0000000000 --- a/examples/01-basic/05-block-manipulation/styles.css +++ /dev/null @@ -1,15 +0,0 @@ -.edit-buttons { - display: flex; - justify-content: space-between; - margin-top: 8px; -} - -.edit-button { - border: 1px solid gray; - border-radius: 4px; - padding-inline: 4px; -} - -.edit-button:hover { - border: 1px solid lightgrey; -} \ No newline at end of file diff --git a/examples/01-basic/05-block-manipulation/tsconfig.json b/examples/01-basic/05-block-manipulation/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/05-block-manipulation/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/05-block-manipulation/vite.config.ts b/examples/01-basic/05-block-manipulation/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/05-block-manipulation/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/05-removing-default-blocks/.bnexample.json b/examples/01-basic/05-removing-default-blocks/.bnexample.json new file mode 100644 index 0000000000..414e193313 --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "hunxjunedo", + "tags": ["Basic", "removing", "blocks"] +} diff --git a/examples/01-basic/05-removing-default-blocks/README.md b/examples/01-basic/05-removing-default-blocks/README.md new file mode 100644 index 0000000000..f950e2ad31 --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/README.md @@ -0,0 +1,9 @@ +# Removing Default Blocks from Schema + +This example shows how to change the default schema and disable the Audio and Image blocks. To do this, we pass in a custom schema based on the built-in, default schema, with two specific blocks removed. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Custom Schemas](/docs/features/custom-schemas) +- [Default Schema](/docs/foundations/schemas) diff --git a/examples/01-basic/05-removing-default-blocks/index.html b/examples/01-basic/05-removing-default-blocks/index.html new file mode 100644 index 0000000000..f95160e28c --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/index.html @@ -0,0 +1,14 @@ + + + + + Removing Default Blocks from Schema + + + +
+ + + diff --git a/examples/01-basic/05-removing-default-blocks/main.tsx b/examples/01-basic/05-removing-default-blocks/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/05-removing-default-blocks/package.json b/examples/01-basic/05-removing-default-blocks/package.json new file mode 100644 index 0000000000..33be32b3ba --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-removing-default-blocks", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/05-removing-default-blocks/src/App.tsx b/examples/01-basic/05-removing-default-blocks/src/App.tsx new file mode 100644 index 0000000000..d56523af3a --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/src/App.tsx @@ -0,0 +1,26 @@ +import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Disable the Audio and Image blocks from the built-in schema + // This is done by picking out the blocks you want to disable + const { audio, image, ...remainingBlockSpecs } = defaultBlockSpecs; + + const schema = BlockNoteSchema.create({ + blockSpecs: { + // remainingBlockSpecs contains all the other blocks + ...remainingBlockSpecs, + }, + }); + + // Creates a new editor instance with the schema + const editor = useCreateBlockNote({ + schema, + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/05-removing-default-blocks/tsconfig.json b/examples/01-basic/05-removing-default-blocks/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/05-removing-default-blocks/vite.config.ts b/examples/01-basic/05-removing-default-blocks/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/05-removing-default-blocks/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/04-selection-blocks/.bnexample.json b/examples/01-basic/06-block-manipulation/.bnexample.json similarity index 100% rename from examples/01-basic/04-selection-blocks/.bnexample.json rename to examples/01-basic/06-block-manipulation/.bnexample.json diff --git a/examples/01-basic/06-block-manipulation/README.md b/examples/01-basic/06-block-manipulation/README.md new file mode 100644 index 0000000000..b09de35dfd --- /dev/null +++ b/examples/01-basic/06-block-manipulation/README.md @@ -0,0 +1,7 @@ +# Manipulating Blocks + +This example shows 4 buttons to manipulate the first block using the `insertBlocks`, `updateBlock`, `removeBlocks` and `replaceBlocks` methods. + +**Relevant Docs:** + +- [Block Manipulation](/docs/reference/editor/manipulating-content) diff --git a/examples/01-basic/06-block-manipulation/index.html b/examples/01-basic/06-block-manipulation/index.html new file mode 100644 index 0000000000..7bdf77e285 --- /dev/null +++ b/examples/01-basic/06-block-manipulation/index.html @@ -0,0 +1,14 @@ + + + + + Manipulating Blocks + + + +
+ + + diff --git a/examples/01-basic/06-block-manipulation/main.tsx b/examples/01-basic/06-block-manipulation/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/06-block-manipulation/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/06-block-manipulation/package.json b/examples/01-basic/06-block-manipulation/package.json new file mode 100644 index 0000000000..6f20d65f17 --- /dev/null +++ b/examples/01-basic/06-block-manipulation/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-block-manipulation", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/06-block-manipulation/src/App.tsx b/examples/01-basic/06-block-manipulation/src/App.tsx new file mode 100644 index 0000000000..b92008ef72 --- /dev/null +++ b/examples/01-basic/06-block-manipulation/src/App.tsx @@ -0,0 +1,74 @@ +import "@blocknote/core/fonts/inter.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +import "./styles.css"; + +export default function App() { + const editor = useCreateBlockNote(); + + return ( +
+ +
+ {/*Inserts a new block at start of document.*/} + + {/*Updates the first block*/} + + {/*Removes the first block*/} + + {/*Replaces the first block*/} + +
+
+ ); +} diff --git a/examples/01-basic/06-block-manipulation/src/styles.css b/examples/01-basic/06-block-manipulation/src/styles.css new file mode 100644 index 0000000000..cc97b34a4f --- /dev/null +++ b/examples/01-basic/06-block-manipulation/src/styles.css @@ -0,0 +1,15 @@ +.edit-buttons { + display: flex; + justify-content: space-between; + margin-top: 8px; +} + +.edit-button { + border: 1px solid gray; + border-radius: 4px; + padding-inline: 4px; +} + +.edit-button:hover { + border: 1px solid lightgrey; +} diff --git a/examples/01-basic/06-block-manipulation/tsconfig.json b/examples/01-basic/06-block-manipulation/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/06-block-manipulation/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/06-block-manipulation/vite.config.ts b/examples/01-basic/06-block-manipulation/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/06-block-manipulation/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/06-file-uploading/App.tsx b/examples/01-basic/06-file-uploading/App.tsx deleted file mode 100644 index 3c32f914c5..0000000000 --- a/examples/01-basic/06-file-uploading/App.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -// Uploads a file to tmpfiles.org and returns the URL to the uploaded file. -async function uploadFile(file: File) { - const body = new FormData(); - body.append("file", file); - - const ret = await fetch("https://tmpfiles.org/api/v1/upload", { - method: "POST", - body: body, - }); - return (await ret.json()).data.url.replace( - "tmpfiles.org/", - "tmpfiles.org/dl/" - ); -} - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Upload an image using the button below", - }, - { - type: "image", - }, - { - type: "paragraph", - }, - ], - uploadFile, - }); - - // Renders the editor instance using a React component. - return ; -} diff --git a/examples/01-basic/06-file-uploading/README.md b/examples/01-basic/06-file-uploading/README.md deleted file mode 100644 index a8ed2c5979..0000000000 --- a/examples/01-basic/06-file-uploading/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Uploading Files - -This example allows users to upload files and use them in the editor. The files are uploaded to [/TMP/Files](https://tmpfiles.org/), and can be used in Image blocks. - -**Try it out:** Click the "Add Image" button and see there's now an "Upload" tab in the toolbar! - -**Relevant Docs:** - -- [Editor Setup](/docs/editor-basics/setup) -- [Image](/docs/editor-basics/default-schema#image) diff --git a/examples/01-basic/06-file-uploading/index.html b/examples/01-basic/06-file-uploading/index.html deleted file mode 100644 index c9c7bf8b2a..0000000000 --- a/examples/01-basic/06-file-uploading/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Uploading Files - - -
- - - diff --git a/examples/01-basic/06-file-uploading/main.tsx b/examples/01-basic/06-file-uploading/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/06-file-uploading/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/06-file-uploading/package.json b/examples/01-basic/06-file-uploading/package.json deleted file mode 100644 index a65756f3bf..0000000000 --- a/examples/01-basic/06-file-uploading/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-file-uploading", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/06-file-uploading/tsconfig.json b/examples/01-basic/06-file-uploading/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/06-file-uploading/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/06-file-uploading/vite.config.ts b/examples/01-basic/06-file-uploading/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/06-file-uploading/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/07-saving-loading/README.md b/examples/01-basic/07-saving-loading/README.md deleted file mode 100644 index d6219b4c74..0000000000 --- a/examples/01-basic/07-saving-loading/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Saving & Loading - -This example shows how to save the editor contents to local storage whenever a change is made, and load the saved contents when the editor is created. - -You can replace the `saveToStorage` and `loadFromStorage` functions with calls to your backend or database. - -**Try it out:** Try typing in the editor and reloading the page! - -**Relevant Docs:** - -- [Editor Setup](/docs/editor-basics/setup) -- [Getting the Document](/docs/editor-api/manipulating-blocks#getting-the-document) diff --git a/examples/01-basic/07-saving-loading/index.html b/examples/01-basic/07-saving-loading/index.html deleted file mode 100644 index 77be86d603..0000000000 --- a/examples/01-basic/07-saving-loading/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Saving & Loading - - -
- - - diff --git a/examples/01-basic/07-saving-loading/main.tsx b/examples/01-basic/07-saving-loading/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/07-saving-loading/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/07-saving-loading/package.json b/examples/01-basic/07-saving-loading/package.json deleted file mode 100644 index 8251e98701..0000000000 --- a/examples/01-basic/07-saving-loading/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-saving-loading", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/07-saving-loading/tsconfig.json b/examples/01-basic/07-saving-loading/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/07-saving-loading/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/07-saving-loading/vite.config.ts b/examples/01-basic/07-saving-loading/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/07-saving-loading/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/05-block-manipulation/.bnexample.json b/examples/01-basic/07-selection-blocks/.bnexample.json similarity index 100% rename from examples/01-basic/05-block-manipulation/.bnexample.json rename to examples/01-basic/07-selection-blocks/.bnexample.json diff --git a/examples/01-basic/07-selection-blocks/README.md b/examples/01-basic/07-selection-blocks/README.md new file mode 100644 index 0000000000..a8d689c24b --- /dev/null +++ b/examples/01-basic/07-selection-blocks/README.md @@ -0,0 +1,9 @@ +# Displaying Selected Blocks + +In this example, the JSON representation of blocks spanned by the user's selection, is displayed below the editor. + +**Try it out:** Select different blocks in the editor and see the JSON update! + +**Relevant Docs:** + +- [Cursor Selections](/docs/reference/editor/cursor-selections) diff --git a/examples/01-basic/07-selection-blocks/index.html b/examples/01-basic/07-selection-blocks/index.html new file mode 100644 index 0000000000..a51458b513 --- /dev/null +++ b/examples/01-basic/07-selection-blocks/index.html @@ -0,0 +1,14 @@ + + + + + Displaying Selected Blocks + + + +
+ + + diff --git a/examples/01-basic/07-selection-blocks/main.tsx b/examples/01-basic/07-selection-blocks/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/07-selection-blocks/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/07-selection-blocks/package.json b/examples/01-basic/07-selection-blocks/package.json new file mode 100644 index 0000000000..be27184ff7 --- /dev/null +++ b/examples/01-basic/07-selection-blocks/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-selection-blocks", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/07-selection-blocks/src/App.tsx b/examples/01-basic/07-selection-blocks/src/App.tsx new file mode 100644 index 0000000000..811b14e328 --- /dev/null +++ b/examples/01-basic/07-selection-blocks/src/App.tsx @@ -0,0 +1,56 @@ +import { Block } from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useState } from "react"; + +import "./styles.css"; + +export default function App() { + // Stores the selected blocks as an array of Block objects. + const [blocks, setBlocks] = useState([]); + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Select different blocks to see the JSON change below", + }, + ], + }); + + // Renders the editor instance. + return ( +
+
BlockNote Editor:
+
+ { + const selection = editor.getSelection(); + + // Get the blocks in the current selection and store on the state. If + // the selection is empty, store the block containing the text cursor + // instead. + if (selection !== undefined) { + setBlocks(selection.blocks); + } else { + setBlocks([editor.getTextCursorPosition().block]); + } + }} + /> +
+
Selection JSON:
+
+
+          {JSON.stringify(blocks, null, 2)}
+        
+
+
+ ); +} diff --git a/examples/01-basic/07-selection-blocks/src/styles.css b/examples/01-basic/07-selection-blocks/src/styles.css new file mode 100644 index 0000000000..6d5eeba7fe --- /dev/null +++ b/examples/01-basic/07-selection-blocks/src/styles.css @@ -0,0 +1,25 @@ +.wrapper { + display: flex; + flex-direction: column; + height: 100%; +} + +.item { + border-radius: 0.5rem; + flex: 1; + overflow: hidden; +} + +.item.bordered { + border: 1px solid gray; +} + +.item pre { + border-radius: 0.5rem; + height: 100%; + overflow: auto; + padding-block: 1rem; + padding-inline: 54px; + width: 100%; + white-space: pre-wrap; +} diff --git a/examples/01-basic/07-selection-blocks/tsconfig.json b/examples/01-basic/07-selection-blocks/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/07-selection-blocks/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/07-selection-blocks/vite.config.ts b/examples/01-basic/07-selection-blocks/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/07-selection-blocks/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/08-shadcn/.bnexample.json b/examples/01-basic/08-ariakit/.bnexample.json similarity index 100% rename from examples/01-basic/08-shadcn/.bnexample.json rename to examples/01-basic/08-ariakit/.bnexample.json diff --git a/examples/01-basic/08-ariakit/README.md b/examples/01-basic/08-ariakit/README.md new file mode 100644 index 0000000000..3eae9a9a01 --- /dev/null +++ b/examples/01-basic/08-ariakit/README.md @@ -0,0 +1,8 @@ +# Use with Ariakit + +This example shows how you can use BlockNote with Ariakit (instead of Mantine). + +**Relevant Docs:** + +- [Ariakit Docs](/docs/getting-started/ariakit) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/01-basic/08-ariakit/index.html b/examples/01-basic/08-ariakit/index.html new file mode 100644 index 0000000000..e4594a8ae2 --- /dev/null +++ b/examples/01-basic/08-ariakit/index.html @@ -0,0 +1,14 @@ + + + + + Use with Ariakit + + + +
+ + + diff --git a/examples/01-basic/08-ariakit/main.tsx b/examples/01-basic/08-ariakit/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/08-ariakit/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/08-ariakit/package.json b/examples/01-basic/08-ariakit/package.json new file mode 100644 index 0000000000..15feccbc93 --- /dev/null +++ b/examples/01-basic/08-ariakit/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-ariakit", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/09-ariakit/App.tsx b/examples/01-basic/08-ariakit/src/App.tsx similarity index 100% rename from examples/01-basic/09-ariakit/App.tsx rename to examples/01-basic/08-ariakit/src/App.tsx diff --git a/examples/01-basic/08-ariakit/tsconfig.json b/examples/01-basic/08-ariakit/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/08-ariakit/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/08-ariakit/vite.config.ts b/examples/01-basic/08-ariakit/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/08-ariakit/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/08-shadcn/App.tsx b/examples/01-basic/08-shadcn/App.tsx deleted file mode 100644 index 754b4a5e94..0000000000 --- a/examples/01-basic/08-shadcn/App.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/shadcn"; -import "@blocknote/shadcn/style.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote(); - - // Renders the editor instance using a React component. - return ( - - ); -} diff --git a/examples/01-basic/08-shadcn/README.md b/examples/01-basic/08-shadcn/README.md deleted file mode 100644 index b585d3e461..0000000000 --- a/examples/01-basic/08-shadcn/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Use with ShadCN - -This example shows how you can use BlockNote with ShadCN (instead of Mantine). - -**Relevant Docs:** - -- [ShadCN](/docs/advanced/shadcn) diff --git a/examples/01-basic/08-shadcn/index.html b/examples/01-basic/08-shadcn/index.html deleted file mode 100644 index c70c418c1a..0000000000 --- a/examples/01-basic/08-shadcn/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Use with ShadCN - - -
- - - diff --git a/examples/01-basic/08-shadcn/main.tsx b/examples/01-basic/08-shadcn/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/08-shadcn/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/08-shadcn/package.json b/examples/01-basic/08-shadcn/package.json deleted file mode 100644 index 2ca186fe27..0000000000 --- a/examples/01-basic/08-shadcn/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-shadcn", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/08-shadcn/tsconfig.json b/examples/01-basic/08-shadcn/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/08-shadcn/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/08-shadcn/vite.config.ts b/examples/01-basic/08-shadcn/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/08-shadcn/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/09-ariakit/README.md b/examples/01-basic/09-ariakit/README.md deleted file mode 100644 index 7ddb27b10a..0000000000 --- a/examples/01-basic/09-ariakit/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Use with Ariakit - -This example shows how you can use BlockNote with Ariakit (instead of Mantine). - -**Relevant Docs:** - -- [Ariakit](/docs/advanced/ariakit) diff --git a/examples/01-basic/09-ariakit/index.html b/examples/01-basic/09-ariakit/index.html deleted file mode 100644 index f394c4992a..0000000000 --- a/examples/01-basic/09-ariakit/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Use with Ariakit - - -
- - - diff --git a/examples/01-basic/09-ariakit/main.tsx b/examples/01-basic/09-ariakit/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/01-basic/09-ariakit/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/01-basic/09-ariakit/package.json b/examples/01-basic/09-ariakit/package.json deleted file mode 100644 index 4f37f955c5..0000000000 --- a/examples/01-basic/09-ariakit/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-ariakit", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/01-basic/09-ariakit/tsconfig.json b/examples/01-basic/09-ariakit/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/01-basic/09-ariakit/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/01-basic/09-ariakit/vite.config.ts b/examples/01-basic/09-ariakit/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/01-basic/09-ariakit/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/01-basic/09-shadcn/.bnexample.json b/examples/01-basic/09-shadcn/.bnexample.json new file mode 100644 index 0000000000..baf1eb6306 --- /dev/null +++ b/examples/01-basic/09-shadcn/.bnexample.json @@ -0,0 +1,8 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": ["Basic"], + "tailwind": true, + "stackBlitz": false +} diff --git a/examples/01-basic/09-shadcn/README.md b/examples/01-basic/09-shadcn/README.md new file mode 100644 index 0000000000..e3030d4929 --- /dev/null +++ b/examples/01-basic/09-shadcn/README.md @@ -0,0 +1,7 @@ +# Use with ShadCN + +This example shows how you can use BlockNote with ShadCN (instead of Mantine). + +**Relevant Docs:** + +- [Getting Started with ShadCN](/docs/getting-started/shadcn) diff --git a/examples/01-basic/09-shadcn/index.html b/examples/01-basic/09-shadcn/index.html new file mode 100644 index 0000000000..84a8096813 --- /dev/null +++ b/examples/01-basic/09-shadcn/index.html @@ -0,0 +1,14 @@ + + + + + Use with ShadCN + + + +
+ + + diff --git a/examples/01-basic/09-shadcn/main.tsx b/examples/01-basic/09-shadcn/main.tsx new file mode 100644 index 0000000000..ae3c3854f3 --- /dev/null +++ b/examples/01-basic/09-shadcn/main.tsx @@ -0,0 +1,12 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; +import "./tailwind.css"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/09-shadcn/package.json b/examples/01-basic/09-shadcn/package.json new file mode 100644 index 0000000000..3b0b996a97 --- /dev/null +++ b/examples/01-basic/09-shadcn/package.json @@ -0,0 +1,33 @@ +{ + "name": "@blocknote/example-basic-shadcn", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwindcss": "^4.1.14", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.14", + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/09-shadcn/src/App.tsx b/examples/01-basic/09-shadcn/src/App.tsx new file mode 100644 index 0000000000..5cd64bb302 --- /dev/null +++ b/examples/01-basic/09-shadcn/src/App.tsx @@ -0,0 +1,20 @@ +import "@blocknote/core/fonts/inter.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/shadcn"; +import "@blocknote/shadcn/style.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote(); + + // Renders the editor instance using a React component. + return ( + + ); +} diff --git a/examples/01-basic/09-shadcn/tailwind.css b/examples/01-basic/09-shadcn/tailwind.css new file mode 100644 index 0000000000..eb17734f35 --- /dev/null +++ b/examples/01-basic/09-shadcn/tailwind.css @@ -0,0 +1,121 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +/* Code below needed for ShadCN examples, check docs for more info. */ +@source "./node_modules/@blocknote/shadcn"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + .bn-shadcn * { + @apply border-border outline-ring/50; + } +} diff --git a/examples/01-basic/09-shadcn/tsconfig.json b/examples/01-basic/09-shadcn/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/09-shadcn/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/09-shadcn/vite.config.ts b/examples/01-basic/09-shadcn/vite.config.ts new file mode 100644 index 0000000000..b2ce77e894 --- /dev/null +++ b/examples/01-basic/09-shadcn/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +import tailwindcss from "@tailwindcss/vite"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react(), tailwindcss()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/10-localization/App.tsx b/examples/01-basic/10-localization/App.tsx deleted file mode 100644 index 72b7557f1f..0000000000 --- a/examples/01-basic/10-localization/App.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { locales } from "@blocknote/core"; -import "@blocknote/core/fonts/inter.css"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useCreateBlockNote } from "@blocknote/react"; -// import { useTranslation } from "some-i18n-library"; // You can use any i18n library you like - -export default function App() { - // const { lang } = useTranslation('common'); // You can get the current language from the i18n library or alternatively from a router - // Creates a new editor instance. - const editor = useCreateBlockNote({ - // Passes the Dutch (NL) dictionary to the editor instance. - // You can also provide your own dictionary here to customize the strings used in the editor, - // or submit a Pull Request to add support for your language of your choice - dictionary: locales.nl, - // dictionary: locales[lang as keyof typeof locales], // Use the language from the i18n library dynamically - }); - - // Renders the editor instance using a React component. - return ; -} diff --git a/examples/01-basic/10-localization/README.md b/examples/01-basic/10-localization/README.md index e1da3ce24b..9dc27ec369 100644 --- a/examples/01-basic/10-localization/README.md +++ b/examples/01-basic/10-localization/README.md @@ -6,4 +6,4 @@ You can also provide your own dictionary to customize the strings used in the ed **Relevant Docs:** -- [Editor Setup](/docs/editor-basics/setup) +- [Localization](/docs/features/localization) diff --git a/examples/01-basic/10-localization/index.html b/examples/01-basic/10-localization/index.html index 5a75a1709e..efa7f80ad3 100644 --- a/examples/01-basic/10-localization/index.html +++ b/examples/01-basic/10-localization/index.html @@ -1,11 +1,11 @@ - Localization (i18n) +
diff --git a/examples/01-basic/10-localization/main.tsx b/examples/01-basic/10-localization/main.tsx index f88b490fbd..1260513388 100644 --- a/examples/01-basic/10-localization/main.tsx +++ b/examples/01-basic/10-localization/main.tsx @@ -1,11 +1,11 @@ // AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY import React from "react"; import { createRoot } from "react-dom/client"; -import App from "./App"; +import App from "./src/App.jsx"; const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/01-basic/10-localization/package.json b/examples/01-basic/10-localization/package.json index 538c4c2ebc..f34f9a15c8 100644 --- a/examples/01-basic/10-localization/package.json +++ b/examples/01-basic/10-localization/package.json @@ -1,37 +1,30 @@ { - "name": "@blocknote/example-localization", + "name": "@blocknote/example-basic-localization", "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", "@blocknote/mantine": "latest", + "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" }, "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/10-localization/src/App.tsx b/examples/01-basic/10-localization/src/App.tsx new file mode 100644 index 0000000000..7000c3e436 --- /dev/null +++ b/examples/01-basic/10-localization/src/App.tsx @@ -0,0 +1,21 @@ +import { nl } from "@blocknote/core/locales"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +// import { useTranslation } from "some-i18n-library"; // You can use any i18n library you like + +export default function App() { + // const { lang } = useTranslation('common'); // You can get the current language from the i18n library or alternatively from a router + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // Passes the Dutch (NL) dictionary to the editor instance. + // You can also provide your own dictionary here to customize the strings used in the editor, + // or submit a Pull Request to add support for your language of your choice + dictionary: nl, + // dictionary: locales[lang as keyof typeof locales], // Use the language from the i18n library dynamically + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/10-localization/tsconfig.json b/examples/01-basic/10-localization/tsconfig.json index 1bd8ab3c57..93fa81bee8 100644 --- a/examples/01-basic/10-localization/tsconfig.json +++ b/examples/01-basic/10-localization/tsconfig.json @@ -3,28 +3,21 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/01-basic/10-localization/vite.config.ts b/examples/01-basic/10-localization/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/01-basic/10-localization/vite.config.ts +++ b/examples/01-basic/10-localization/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/01-basic/11-custom-placeholder/.bnexample.json b/examples/01-basic/11-custom-placeholder/.bnexample.json new file mode 100644 index 0000000000..93dd5d3de3 --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "ezhil56x", + "tags": ["Basic"] +} diff --git a/examples/01-basic/11-custom-placeholder/README.md b/examples/01-basic/11-custom-placeholder/README.md new file mode 100644 index 0000000000..db435d7eee --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/README.md @@ -0,0 +1,12 @@ +# Change placeholder text + +In this example, we show how to change the placeholders: + +- For an empty document, we show a placeholder `Start typing..` (by default, this is not set) +- the default placeholder in this editor shows `Custom default placeholder` instead of the default (`Enter text or type '/' for commands`) +- for Headings, the placeholder shows `Custom heading placeholder` instead of the default (`Heading`). Try adding a Heading to see the change + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Localization (i18n)](/examples/basic/localization) diff --git a/examples/01-basic/11-custom-placeholder/index.html b/examples/01-basic/11-custom-placeholder/index.html new file mode 100644 index 0000000000..24c535465c --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/index.html @@ -0,0 +1,14 @@ + + + + + Change placeholder text + + + +
+ + + diff --git a/examples/01-basic/11-custom-placeholder/main.tsx b/examples/01-basic/11-custom-placeholder/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/11-custom-placeholder/package.json b/examples/01-basic/11-custom-placeholder/package.json new file mode 100644 index 0000000000..8a2daf2c95 --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-custom-placeholder", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/11-custom-placeholder/src/App.tsx b/examples/01-basic/11-custom-placeholder/src/App.tsx new file mode 100644 index 0000000000..b1323aacb5 --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/src/App.tsx @@ -0,0 +1,30 @@ +import { en } from "@blocknote/core/locales"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // We use the English, default dictionary + const locale = en; + + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // We override the `placeholders` in our dictionary + dictionary: { + ...locale, + placeholders: { + ...locale.placeholders, + // We override the empty document placeholder + emptyDocument: "Start typing..", + // We override the default placeholder + default: "Custom default placeholder", + // We override the heading placeholder + heading: "Custom heading placeholder", + }, + }, + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/11-custom-placeholder/tsconfig.json b/examples/01-basic/11-custom-placeholder/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/11-custom-placeholder/vite.config.ts b/examples/01-basic/11-custom-placeholder/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/11-custom-placeholder/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/12-multi-editor/.bnexample.json b/examples/01-basic/12-multi-editor/.bnexample.json new file mode 100644 index 0000000000..54cfd20571 --- /dev/null +++ b/examples/01-basic/12-multi-editor/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "areknawo", + "tags": ["Basic"] +} diff --git a/examples/01-basic/12-multi-editor/README.md b/examples/01-basic/12-multi-editor/README.md new file mode 100644 index 0000000000..b2da731dd0 --- /dev/null +++ b/examples/01-basic/12-multi-editor/README.md @@ -0,0 +1,7 @@ +# Multi-Editor Setup + +This example showcases use of multiple editors in a single page - you can even drag blocks between them. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/01-basic/12-multi-editor/index.html b/examples/01-basic/12-multi-editor/index.html new file mode 100644 index 0000000000..011941dc50 --- /dev/null +++ b/examples/01-basic/12-multi-editor/index.html @@ -0,0 +1,14 @@ + + + + + Multi-Editor Setup + + + +
+ + + diff --git a/examples/01-basic/12-multi-editor/main.tsx b/examples/01-basic/12-multi-editor/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/12-multi-editor/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/12-multi-editor/package.json b/examples/01-basic/12-multi-editor/package.json new file mode 100644 index 0000000000..857456a1ec --- /dev/null +++ b/examples/01-basic/12-multi-editor/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-multi-editor", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/12-multi-editor/src/App.tsx b/examples/01-basic/12-multi-editor/src/App.tsx new file mode 100644 index 0000000000..994e98830f --- /dev/null +++ b/examples/01-basic/12-multi-editor/src/App.tsx @@ -0,0 +1,55 @@ +import { PartialBlock } from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +// Component that creates & renders a BlockNote editor. +function Editor(props: { + initialContent?: PartialBlock[]; + theme: "dark" | "light"; +}) { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: props.initialContent, + }); + + // Renders the editor instance using a React component. + return ( + + ); +} + +export default function App() { + // Creates & renders two editors side by side. + return ( +
+ + +
+ ); +} diff --git a/examples/01-basic/12-multi-editor/tsconfig.json b/examples/01-basic/12-multi-editor/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/12-multi-editor/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/12-multi-editor/vite.config.ts b/examples/01-basic/12-multi-editor/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/12-multi-editor/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/13-custom-paste-handler/.bnexample.json b/examples/01-basic/13-custom-paste-handler/.bnexample.json new file mode 100644 index 0000000000..5778b19ace --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["Basic"] +} diff --git a/examples/01-basic/13-custom-paste-handler/README.md b/examples/01-basic/13-custom-paste-handler/README.md new file mode 100644 index 0000000000..c19f9c2bbf --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/README.md @@ -0,0 +1,9 @@ +# Custom Paste Handler + +In this example, we change the default paste handler to append some text to the pasted content when the content is plain text. + +**Try it out:** Use the buttons to copy some content to the clipboard and paste it in the editor to trigger our custom paste handler. + +**Relevant Docs:** + +- [Paste Handling](/docs/reference/editor/paste-handling) diff --git a/examples/01-basic/13-custom-paste-handler/index.html b/examples/01-basic/13-custom-paste-handler/index.html new file mode 100644 index 0000000000..b68dcceeba --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/index.html @@ -0,0 +1,14 @@ + + + + + Custom Paste Handler + + + +
+ + + diff --git a/examples/01-basic/13-custom-paste-handler/main.tsx b/examples/01-basic/13-custom-paste-handler/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/13-custom-paste-handler/package.json b/examples/01-basic/13-custom-paste-handler/package.json new file mode 100644 index 0000000000..35bfc0d2a7 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-custom-paste-handler", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/13-custom-paste-handler/src/App.tsx b/examples/01-basic/13-custom-paste-handler/src/App.tsx new file mode 100644 index 0000000000..a817ac4808 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/src/App.tsx @@ -0,0 +1,109 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +import "./styles.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: [ + { + styles: {}, + type: "text", + text: "Paste some text here", + }, + ], + }, + ], + pasteHandler: ({ event, editor, defaultPasteHandler }) => { + if (event.clipboardData?.types.includes("text/plain")) { + editor.pasteMarkdown( + event.clipboardData.getData("text/plain") + + " - inserted by the custom paste handler", + ); + return true; + } + return defaultPasteHandler(); + }, + }); + + // Renders the editor instance using a React component. + return ( +
+ +
+ + + + +
+
+ ); +} diff --git a/examples/01-basic/13-custom-paste-handler/src/styles.css b/examples/01-basic/13-custom-paste-handler/src/styles.css new file mode 100644 index 0000000000..cc97b34a4f --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/src/styles.css @@ -0,0 +1,15 @@ +.edit-buttons { + display: flex; + justify-content: space-between; + margin-top: 8px; +} + +.edit-button { + border: 1px solid gray; + border-radius: 4px; + padding-inline: 4px; +} + +.edit-button:hover { + border: 1px solid lightgrey; +} diff --git a/examples/01-basic/13-custom-paste-handler/tsconfig.json b/examples/01-basic/13-custom-paste-handler/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/13-custom-paste-handler/vite.config.ts b/examples/01-basic/13-custom-paste-handler/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/13-custom-paste-handler/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/14-editor-scrollable/.bnexample.json b/examples/01-basic/14-editor-scrollable/.bnexample.json new file mode 100644 index 0000000000..e9c8bcb27b --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Basic"] +} diff --git a/examples/01-basic/14-editor-scrollable/README.md b/examples/01-basic/14-editor-scrollable/README.md new file mode 100644 index 0000000000..0ae571ad1f --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/README.md @@ -0,0 +1,7 @@ +# Scrollable Editor + +This example shows how to constrain the editor height and make it scrollable. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/01-basic/14-editor-scrollable/index.html b/examples/01-basic/14-editor-scrollable/index.html new file mode 100644 index 0000000000..54b1f87c4a --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/index.html @@ -0,0 +1,14 @@ + + + + + Scrollable Editor + + + +
+ + + diff --git a/examples/01-basic/14-editor-scrollable/main.tsx b/examples/01-basic/14-editor-scrollable/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/14-editor-scrollable/package.json b/examples/01-basic/14-editor-scrollable/package.json new file mode 100644 index 0000000000..b7fd5c65ac --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-editor-scrollable", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/14-editor-scrollable/src/App.tsx b/examples/01-basic/14-editor-scrollable/src/App.tsx new file mode 100644 index 0000000000..c7e7e2e18d --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/src/App.tsx @@ -0,0 +1,14 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +import "./style.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote(); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/14-editor-scrollable/src/style.css b/examples/01-basic/14-editor-scrollable/src/style.css new file mode 100644 index 0000000000..478d974662 --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/src/style.css @@ -0,0 +1,4 @@ +.bn-editor { + height: 500px; + overflow: auto; +} diff --git a/examples/01-basic/14-editor-scrollable/tsconfig.json b/examples/01-basic/14-editor-scrollable/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/14-editor-scrollable/vite.config.ts b/examples/01-basic/14-editor-scrollable/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/14-editor-scrollable/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/15-shadowdom/.bnexample.json b/examples/01-basic/15-shadowdom/.bnexample.json new file mode 100644 index 0000000000..e9c8bcb27b --- /dev/null +++ b/examples/01-basic/15-shadowdom/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Basic"] +} diff --git a/examples/01-basic/15-shadowdom/README.md b/examples/01-basic/15-shadowdom/README.md new file mode 100644 index 0000000000..caf50933e0 --- /dev/null +++ b/examples/01-basic/15-shadowdom/README.md @@ -0,0 +1,7 @@ +# Shadow DOM + +This example shows how to render the BlockNote editor inside a Shadow DOM. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/01-basic/15-shadowdom/index.html b/examples/01-basic/15-shadowdom/index.html new file mode 100644 index 0000000000..9e5d5d45ad --- /dev/null +++ b/examples/01-basic/15-shadowdom/index.html @@ -0,0 +1,14 @@ + + + + + Shadow DOM + + + +
+ + + diff --git a/examples/01-basic/15-shadowdom/main.tsx b/examples/01-basic/15-shadowdom/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/15-shadowdom/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/15-shadowdom/package.json b/examples/01-basic/15-shadowdom/package.json new file mode 100644 index 0000000000..15bbd09d12 --- /dev/null +++ b/examples/01-basic/15-shadowdom/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-shadowdom", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/15-shadowdom/src/App.tsx b/examples/01-basic/15-shadowdom/src/App.tsx new file mode 100644 index 0000000000..e265e8ecf9 --- /dev/null +++ b/examples/01-basic/15-shadowdom/src/App.tsx @@ -0,0 +1,47 @@ +import { BlockNoteView } from "@blocknote/mantine"; +import { useCreateBlockNote } from "@blocknote/react"; + +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; + +import interCss from "@blocknote/core/fonts/inter.css?inline"; +import mantineCss from "@blocknote/mantine/style.css?inline"; + +function ShadowWrapper(props: { children: React.ReactNode }) { + const host = useRef(null); + const [shadowRoot, setShadowRoot] = useState(null); + + useEffect(() => { + if (host.current && !shadowRoot) { + const root = + host.current.shadowRoot || host.current.attachShadow({ mode: "open" }); + setShadowRoot(root); + } + }, [shadowRoot]); + + return ( +
+ {shadowRoot && + createPortal( + <> + + + {props.children} + , + shadowRoot as any, + )} +
+ ); +} + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote(); + + // Renders the editor instance using a React component. + return ( + + + + ); +} diff --git a/examples/01-basic/15-shadowdom/src/vite-env.d.ts b/examples/01-basic/15-shadowdom/src/vite-env.d.ts new file mode 100644 index 0000000000..0f15c6551b --- /dev/null +++ b/examples/01-basic/15-shadowdom/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module "*?inline" { + const content: string; + export default content; +} diff --git a/examples/01-basic/15-shadowdom/tsconfig.json b/examples/01-basic/15-shadowdom/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/15-shadowdom/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/15-shadowdom/vite.config.ts b/examples/01-basic/15-shadowdom/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/15-shadowdom/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/09-ariakit/.bnexample.json b/examples/01-basic/16-read-only-editor/.bnexample.json similarity index 100% rename from examples/01-basic/09-ariakit/.bnexample.json rename to examples/01-basic/16-read-only-editor/.bnexample.json diff --git a/examples/01-basic/16-read-only-editor/README.md b/examples/01-basic/16-read-only-editor/README.md new file mode 100644 index 0000000000..38d6b91fef --- /dev/null +++ b/examples/01-basic/16-read-only-editor/README.md @@ -0,0 +1,9 @@ +# Read-only Editor + +This example makes the editor read-only while showing the same content as the [Default Schema Showcase](/examples/basic/default-blocks) example. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Document Structure](/docs/foundations/document-structure) +- [Default Schema](/docs/foundations/schemas) diff --git a/examples/01-basic/16-read-only-editor/index.html b/examples/01-basic/16-read-only-editor/index.html new file mode 100644 index 0000000000..66836dd166 --- /dev/null +++ b/examples/01-basic/16-read-only-editor/index.html @@ -0,0 +1,14 @@ + + + + + Read-only Editor + + + +
+ + + diff --git a/examples/01-basic/16-read-only-editor/main.tsx b/examples/01-basic/16-read-only-editor/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/16-read-only-editor/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/16-read-only-editor/package.json b/examples/01-basic/16-read-only-editor/package.json new file mode 100644 index 0000000000..fdec4867ce --- /dev/null +++ b/examples/01-basic/16-read-only-editor/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-read-only-editor", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/16-read-only-editor/src/App.tsx b/examples/01-basic/16-read-only-editor/src/App.tsx new file mode 100644 index 0000000000..b7a1ef9d00 --- /dev/null +++ b/examples/01-basic/16-read-only-editor/src/App.tsx @@ -0,0 +1,153 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Blocks:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: "Paragraph", + }, + { + type: "heading", + content: "Heading", + }, + { + id: "toggle-heading", + type: "heading", + props: { isToggleable: true }, + content: "Toggle Heading", + }, + { + type: "quote", + content: "Quote", + }, + { + type: "bulletListItem", + content: "Bullet List Item", + }, + { + type: "numberedListItem", + content: "Numbered List Item", + }, + { + type: "checkListItem", + content: "Check List Item", + }, + { + id: "toggle-list-item", + type: "toggleListItem", + content: "Toggle List Item", + }, + { + type: "codeBlock", + props: { language: "javascript" }, + content: "console.log('Hello, world!');", + }, + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Table Cell", "Table Cell", "Table Cell"], + }, + { + cells: ["Table Cell", "Table Cell", "Table Cell"], + }, + { + cells: ["Table Cell", "Table Cell", "Table Cell"], + }, + ], + }, + }, + { + type: "file", + }, + { + type: "image", + props: { + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", + }, + }, + { + type: "video", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + }, + }, + { + type: "audio", + props: { + url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + caption: + "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + }, + }, + { + type: "paragraph", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline Content:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Styled Text", + styles: { + bold: true, + italic: true, + textColor: "red", + backgroundColor: "blue", + }, + }, + { + type: "text", + text: " ", + styles: {}, + }, + { + type: "link", + content: "Link", + href: "https://www.blocknotejs.org", + }, + ], + }, + ], + }); + + // Renders the editor instance using a React component and makes it read-only. + return ; +} diff --git a/examples/01-basic/16-read-only-editor/tsconfig.json b/examples/01-basic/16-read-only-editor/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/16-read-only-editor/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/16-read-only-editor/vite.config.ts b/examples/01-basic/16-read-only-editor/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/16-read-only-editor/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/17-no-trailing-block/.bnexample.json b/examples/01-basic/17-no-trailing-block/.bnexample.json new file mode 100644 index 0000000000..e9c8bcb27b --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Basic"] +} diff --git a/examples/01-basic/17-no-trailing-block/README.md b/examples/01-basic/17-no-trailing-block/README.md new file mode 100644 index 0000000000..9c63e46fdd --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/README.md @@ -0,0 +1,7 @@ +# No Trailing Block + +This example shows how to disable the automatic creation of a trailing block at the end of the editor by setting the `trailingBlock` option to `false`. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/01-basic/17-no-trailing-block/index.html b/examples/01-basic/17-no-trailing-block/index.html new file mode 100644 index 0000000000..a86933f050 --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/index.html @@ -0,0 +1,14 @@ + + + + + No Trailing Block + + + +
+ + + diff --git a/examples/01-basic/17-no-trailing-block/main.tsx b/examples/01-basic/17-no-trailing-block/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/01-basic/17-no-trailing-block/package.json b/examples/01-basic/17-no-trailing-block/package.json new file mode 100644 index 0000000000..d3589b4fd0 --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-basic-no-trailing-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/17-no-trailing-block/src/App.tsx b/examples/01-basic/17-no-trailing-block/src/App.tsx new file mode 100644 index 0000000000..ac51ec74b3 --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/src/App.tsx @@ -0,0 +1,14 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + trailingBlock: false, + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/17-no-trailing-block/tsconfig.json b/examples/01-basic/17-no-trailing-block/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/01-basic/17-no-trailing-block/vite.config.ts b/examples/01-basic/17-no-trailing-block/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/testing/README.md b/examples/01-basic/testing/README.md index 9fae60ec9f..04d01f3847 100644 --- a/examples/01-basic/testing/README.md +++ b/examples/01-basic/testing/README.md @@ -1,3 +1,3 @@ # Test Editor -This example is meant for use in end-to-end tests. \ No newline at end of file +This example is meant for use in end-to-end tests. diff --git a/examples/01-basic/testing/index.html b/examples/01-basic/testing/index.html index 32f9b9ee34..b43d92fb67 100644 --- a/examples/01-basic/testing/index.html +++ b/examples/01-basic/testing/index.html @@ -1,11 +1,11 @@ - Test Editor +
diff --git a/examples/01-basic/testing/main.tsx b/examples/01-basic/testing/main.tsx index f88b490fbd..1260513388 100644 --- a/examples/01-basic/testing/main.tsx +++ b/examples/01-basic/testing/main.tsx @@ -1,11 +1,11 @@ // AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY import React from "react"; import { createRoot } from "react-dom/client"; -import App from "./App"; +import App from "./src/App.jsx"; const root = createRoot(document.getElementById("root")!); root.render( - + , ); diff --git a/examples/01-basic/testing/package.json b/examples/01-basic/testing/package.json index 1bce8378e5..46b037471e 100644 --- a/examples/01-basic/testing/package.json +++ b/examples/01-basic/testing/package.json @@ -1,37 +1,30 @@ { - "name": "@blocknote/example-testing", + "name": "@blocknote/example-basic-testing", "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", "private": true, "version": "0.12.4", "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" }, "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", "@blocknote/mantine": "latest", + "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" }, "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/testing/App.tsx b/examples/01-basic/testing/src/App.tsx similarity index 100% rename from examples/01-basic/testing/App.tsx rename to examples/01-basic/testing/src/App.tsx diff --git a/examples/01-basic/testing/tsconfig.json b/examples/01-basic/testing/tsconfig.json index 1bd8ab3c57..93fa81bee8 100644 --- a/examples/01-basic/testing/tsconfig.json +++ b/examples/01-basic/testing/tsconfig.json @@ -3,28 +3,21 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "composite": true }, - "include": [ - "." - ], + "include": ["."], "__ADD_FOR_LOCAL_DEV_references": [ { "path": "../../../packages/core/" @@ -33,4 +26,4 @@ "path": "../../../packages/react/" } ] -} \ No newline at end of file +} diff --git a/examples/01-basic/testing/vite.config.ts b/examples/01-basic/testing/vite.config.ts index f62ab20bc2..0133a6da9e 100644 --- a/examples/01-basic/testing/vite.config.ts +++ b/examples/01-basic/testing/vite.config.ts @@ -2,10 +2,9 @@ import react from "@vitejs/plugin-react"; import * as fs from "fs"; import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; +import { defineConfig } from "vite-plus"; // https://vitejs.dev/config/ -export default defineConfig((conf) => ({ +export default defineConfig(((conf: { command: string }) => ({ plugins: [react()], optimizeDeps: {}, build: { @@ -21,12 +20,12 @@ export default defineConfig((conf) => ({ // or, keep as is to load live from sources with live reload working "@blocknote/core": path.resolve( __dirname, - "../../packages/core/src/" + "../../packages/core/src/", ), "@blocknote/react": path.resolve( __dirname, - "../../packages/react/src/" + "../../packages/react/src/", ), } as any), }, -})); +})) as Parameters[0]); diff --git a/examples/01-basic/06-file-uploading/.bnexample.json b/examples/02-backend/01-file-uploading/.bnexample.json similarity index 100% rename from examples/01-basic/06-file-uploading/.bnexample.json rename to examples/02-backend/01-file-uploading/.bnexample.json diff --git a/examples/02-backend/01-file-uploading/README.md b/examples/02-backend/01-file-uploading/README.md new file mode 100644 index 0000000000..7c471cf83a --- /dev/null +++ b/examples/02-backend/01-file-uploading/README.md @@ -0,0 +1,10 @@ +# Upload Files + +This example allows users to upload files and use them in the editor. The files are uploaded to [/TMP/Files](https://tmpfiles.org/), and can be used for File, Image, Video, and Audio blocks. + +**Try it out:** Click the "Add Image" button and see there's now an "Upload" tab in the toolbar! + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [File Block](/docs/features/blocks/embeds#file) diff --git a/examples/02-backend/01-file-uploading/index.html b/examples/02-backend/01-file-uploading/index.html new file mode 100644 index 0000000000..7d5ab8b393 --- /dev/null +++ b/examples/02-backend/01-file-uploading/index.html @@ -0,0 +1,14 @@ + + + + + Upload Files + + + +
+ + + diff --git a/examples/02-backend/01-file-uploading/main.tsx b/examples/02-backend/01-file-uploading/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/02-backend/01-file-uploading/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/02-backend/01-file-uploading/package.json b/examples/02-backend/01-file-uploading/package.json new file mode 100644 index 0000000000..a79abb1430 --- /dev/null +++ b/examples/02-backend/01-file-uploading/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-backend-file-uploading", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/02-backend/01-file-uploading/src/App.tsx b/examples/02-backend/01-file-uploading/src/App.tsx new file mode 100644 index 0000000000..982d0f37a6 --- /dev/null +++ b/examples/02-backend/01-file-uploading/src/App.tsx @@ -0,0 +1,42 @@ +import "@blocknote/core/fonts/inter.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +// Uploads a file to tmpfiles.org and returns the URL to the uploaded file. +async function uploadFile(file: File) { + const body = new FormData(); + body.append("file", file); + + const ret = await fetch("https://tmpfiles.org/api/v1/upload", { + method: "POST", + body: body, + }); + return (await ret.json()).data.url.replace( + "tmpfiles.org/", + "tmpfiles.org/dl/", + ); +} + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Upload an image using the button below", + }, + { + type: "image", + }, + ], + uploadFile, + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/02-backend/01-file-uploading/tsconfig.json b/examples/02-backend/01-file-uploading/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/02-backend/01-file-uploading/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/02-backend/01-file-uploading/vite.config.ts b/examples/02-backend/01-file-uploading/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/02-backend/01-file-uploading/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/01-basic/07-saving-loading/.bnexample.json b/examples/02-backend/02-saving-loading/.bnexample.json similarity index 100% rename from examples/01-basic/07-saving-loading/.bnexample.json rename to examples/02-backend/02-saving-loading/.bnexample.json diff --git a/examples/02-backend/02-saving-loading/README.md b/examples/02-backend/02-saving-loading/README.md new file mode 100644 index 0000000000..29a853a6bf --- /dev/null +++ b/examples/02-backend/02-saving-loading/README.md @@ -0,0 +1,12 @@ +# Saving & Loading + +This example shows how to save the editor contents to local storage whenever a change is made, and load the saved contents when the editor is created. + +You can replace the `saveToStorage` and `loadFromStorage` functions with calls to your backend or database. + +**Try it out:** Try typing in the editor and reloading the page! + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Getting the Document](/docs/foundations/manipulating-content#reading-blocks) diff --git a/examples/02-backend/02-saving-loading/index.html b/examples/02-backend/02-saving-loading/index.html new file mode 100644 index 0000000000..b6c60cab73 --- /dev/null +++ b/examples/02-backend/02-saving-loading/index.html @@ -0,0 +1,14 @@ + + + + + Saving & Loading + + + +
+ + + diff --git a/examples/02-backend/02-saving-loading/main.tsx b/examples/02-backend/02-saving-loading/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/02-backend/02-saving-loading/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/02-backend/02-saving-loading/package.json b/examples/02-backend/02-saving-loading/package.json new file mode 100644 index 0000000000..7c0f611586 --- /dev/null +++ b/examples/02-backend/02-saving-loading/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-backend-saving-loading", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/01-basic/07-saving-loading/App.tsx b/examples/02-backend/02-saving-loading/src/App.tsx similarity index 100% rename from examples/01-basic/07-saving-loading/App.tsx rename to examples/02-backend/02-saving-loading/src/App.tsx diff --git a/examples/02-backend/02-saving-loading/tsconfig.json b/examples/02-backend/02-saving-loading/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/02-backend/02-saving-loading/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/02-backend/02-saving-loading/vite.config.ts b/examples/02-backend/02-saving-loading/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/02-backend/02-saving-loading/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/02-backend/03-s3/.bnexample.json b/examples/02-backend/03-s3/.bnexample.json new file mode 100644 index 0000000000..7fa3b8f62c --- /dev/null +++ b/examples/02-backend/03-s3/.bnexample.json @@ -0,0 +1,11 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": ["Intermediate", "Saving/Loading"], + "dependencies": { + "@aws-sdk/client-s3": "^3.609.0", + "@aws-sdk/s3-request-presigner": "^3.609.0" + }, + "pro": true +} diff --git a/examples/02-backend/03-s3/README.md b/examples/02-backend/03-s3/README.md new file mode 100644 index 0000000000..f9970e93cf --- /dev/null +++ b/examples/02-backend/03-s3/README.md @@ -0,0 +1,10 @@ +# Upload Files to AWS S3 + +This example allows users to upload files to an AWS S3 bucket and use them in the editor. The files can be used for File, Image, Video, and Audio blocks. + +**Try it out:** Click the "Add Image" button and see there's now an "Upload" tab in the toolbar! + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [File Block](/docs/features/blocks/embeds#file) diff --git a/examples/02-backend/03-s3/index.html b/examples/02-backend/03-s3/index.html new file mode 100644 index 0000000000..853f6527c4 --- /dev/null +++ b/examples/02-backend/03-s3/index.html @@ -0,0 +1,14 @@ + + + + + Upload Files to AWS S3 + + + +
+ + + diff --git a/examples/02-backend/03-s3/main.tsx b/examples/02-backend/03-s3/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/02-backend/03-s3/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json new file mode 100644 index 0000000000..6e2276998d --- /dev/null +++ b/examples/02-backend/03-s3/package.json @@ -0,0 +1,32 @@ +{ + "name": "@blocknote/example-backend-s3", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@aws-sdk/client-s3": "^3.609.0", + "@aws-sdk/s3-request-presigner": "^3.609.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/02-backend/03-s3/src/App.tsx b/examples/02-backend/03-s3/src/App.tsx new file mode 100644 index 0000000000..6f6731ec7e --- /dev/null +++ b/examples/02-backend/03-s3/src/App.tsx @@ -0,0 +1,150 @@ +import { + GetObjectCommand, + // GetObjectCommand, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { + S3RequestPresigner, + getSignedUrl, +} from "@aws-sdk/s3-request-presigner"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +/** + * SERVER Code. Normally, this part would be implemented on your server, so you + * can hide your AWS credentials and control access to your S3 bucket. + * + * In our demo, we are using a public S3 bucket so everything can be done in + * the client. + */ + +// Set up the AWS SDK. +const client = new S3Client({ + region: "us-east-1", + credentials: { + accessKeyId: "", + secretAccessKey: "", + }, +}); + +/** + * The method on the server that generates a pre-signed URL for a PUT request. + */ +const SERVER_createPresignedUrlPUT = (opts: { + bucket: string; + key: string; +}) => { + // This function would normally be implemented on your server. Of course, you + // should make sure the calling user is authenticated, etc. + const { bucket, key } = opts; + const command = new PutObjectCommand({ + Bucket: bucket, + Key: key, + }); + return getSignedUrl(client, command, { expiresIn: 3600 }); +}; + +/** + * The method on the server that generates a pre-signed URL for a GET request. + */ +const SERVER_createPresignedUrlGET = (opts: { + bucket: string; + key: string; +}) => { + // This function would normally be implemented on your server. Of course, you + // should make sure the calling user is authenticated, etc. + const { bucket, key } = opts; + const command = new GetObjectCommand({ + Bucket: bucket, + Key: key, + }); + return getSignedUrl(client, command, { expiresIn: 3600 }); +}; + +/** + * CLIENT code + */ +export default function App() { + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Upload an image to S3 using the button below", + }, + { + type: "image", + }, + ], + uploadFile: async (file) => { + /** + * This function is called by BlockNote whenever it wants to upload a + * file. In this implementation, we are uploading the file to an S3 bucket + * by first requesting an upload URL from the server. + */ + const bucket = "blocknote-demo"; + const key = file.name; + + // Get a URL to upload to from the server. + const signedUrl = await SERVER_createPresignedUrlPUT({ + bucket, + key, + }); + + const headers: any = {}; + if (file?.type) { + // S3 requires setting the correct content type. + headers["Content-Type"] = file!.type || "application/octet-stream"; + } + + // Actually upload the file. + const uploaded = await fetch(signedUrl, { + method: "PUT", + body: file, + headers, + }); + + if (!uploaded.ok) { + throw new Error("Failed to upload file"); + } + + // We store the URL in a custom format, in this case s3://bucket/key. + // We'll subsequently parse this URL in the resolveFileUrl function. + return `s3://${bucket}/${key}`; + }, + resolveFileUrl: async (url) => { + /** + * This function is called by BlockNote whenever it needs to use URL from + * a file block. For example, when displaying an image or downloading a + * file. + * + * In this implementation, we are parsing our custom format and return a + * signed URL from our backend. + */ + if (url.startsWith("s3:")) { + // it's our custom format, request a signed url from the backend + const [, , bucket, key] = url.split("/", 4); + const presignedUrl = await SERVER_createPresignedUrlGET({ + bucket, + key, + }); + return presignedUrl; + } + + return url; + }, + }); + + // Renders the editor instance. + return ; +} + +// This is a hack to make sure the S3RequestPresigner is not used (our demo +// bucket is configured for anonymous access). Remove this in your own code. +S3RequestPresigner.prototype.presign = (request: any) => request; diff --git a/examples/02-backend/03-s3/tsconfig.json b/examples/02-backend/03-s3/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/02-backend/03-s3/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/02-backend/03-s3/vite.config.ts b/examples/02-backend/03-s3/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/02-backend/03-s3/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/02-backend/04-rendering-static-documents/.bnexample.json b/examples/02-backend/04-rendering-static-documents/.bnexample.json new file mode 100644 index 0000000000..a4986d761b --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": ["server"], + "dependencies": { + "@blocknote/server-util": "latest" + } +} diff --git a/examples/02-backend/04-rendering-static-documents/README.md b/examples/02-backend/04-rendering-static-documents/README.md new file mode 100644 index 0000000000..c24c4ca6be --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/README.md @@ -0,0 +1,7 @@ +# Rendering static documents + +This example shows how you can use HTML exported using the `blocksToFullHTML` and render it as a static document (a view-only document, without the editor). You can use this for example if you use BlockNote to edit blog posts in a CMS, but want to display non-editable static, published pages to end-users. + +**Relevant Docs:** + +- [Server-side processing](/docs/features/server-processing) diff --git a/examples/02-backend/04-rendering-static-documents/index.html b/examples/02-backend/04-rendering-static-documents/index.html new file mode 100644 index 0000000000..a62a394dd5 --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/index.html @@ -0,0 +1,14 @@ + + + + + Rendering static documents + + + +
+ + + diff --git a/examples/02-backend/04-rendering-static-documents/main.tsx b/examples/02-backend/04-rendering-static-documents/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json new file mode 100644 index 0000000000..3377797379 --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-backend-rendering-static-documents", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@blocknote/server-util": "latest" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/02-backend/04-rendering-static-documents/src/App.tsx b/examples/02-backend/04-rendering-static-documents/src/App.tsx new file mode 100644 index 0000000000..3a5db9891b --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/src/App.tsx @@ -0,0 +1,67 @@ +import "@blocknote/core/fonts/inter.css"; +import "@blocknote/mantine/style.css"; + +/** + On Server Side, you can use the ServerBlockNoteEditor to render BlockNote documents to HTML. e.g.: + + import { ServerBlockNoteEditor } from "@blocknote/server-util"; + + const editor = ServerBlockNoteEditor.create(); + const html = await editor.blocksToFullHTML(document); + +You can then use render this HTML as a static page or send it to the client. Make sure to include the editor stylesheets: + + import "@blocknote/core/fonts/inter.css"; + // Depending on the UI library you're using, you may want to use `react`, + // `mantine`, etc instead of `core`. + import "@blocknote/core/style.css"; + +This example has the HTML hard-coded, but shows at least how the document will be rendered when the appropriate style sheets are loaded. + */ + +export default function App() { + // This HTML is generated by the ServerBlockNoteEditor.blocksToFullHTML method + const html = `
+
+
+
+

+ Heading 2 +

+
+
+
+
+
+

Paragraph

+
+
+
+
+
+
+

list item

+
+
+
+
+
+
+
+`; + + // Renders the editor instance using a React component. + return ( + // To make the HTML look identical to the editor, we need to add these two + // wrapping divs to the exported blocks. You need will need to add + // additional class names/attributes depend on the UI library you're using, + // whether you want to show light or dark more, etc. It's easiest to just + // check the rendered editor HTML to see what you need to add. +
+
+
+ ); +} diff --git a/examples/02-backend/04-rendering-static-documents/tsconfig.json b/examples/02-backend/04-rendering-static-documents/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/02-backend/04-rendering-static-documents/vite.config.ts b/examples/02-backend/04-rendering-static-documents/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/02-backend/04-rendering-static-documents/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/02-ui-components/01-ui-elements-remove/App.tsx b/examples/02-ui-components/01-ui-elements-remove/App.tsx deleted file mode 100644 index bb4c1176da..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/App.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useCreateBlockNote } from "@blocknote/react"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: - "There are no menus or toolbars in this editor, but you can still markup text using keyboard shortcuts.", - }, - { - type: "paragraph", - content: - "Try making text bold with Ctrl+B/Cmd+B or undo with Ctrl+Z/Cmd+Z.", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - ); -} diff --git a/examples/02-ui-components/01-ui-elements-remove/README.md b/examples/02-ui-components/01-ui-elements-remove/README.md deleted file mode 100644 index 55078381b0..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Removing UI Elements - -In this example, we remove all menus & toolbars, leaving only the editor. - -**Relevant Docs:** - -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/01-ui-elements-remove/index.html b/examples/02-ui-components/01-ui-elements-remove/index.html deleted file mode 100644 index 885854af13..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Removing UI Elements - - -
- - - diff --git a/examples/02-ui-components/01-ui-elements-remove/main.tsx b/examples/02-ui-components/01-ui-elements-remove/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/01-ui-elements-remove/package.json b/examples/02-ui-components/01-ui-elements-remove/package.json deleted file mode 100644 index de49322311..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-ui-elements-remove", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/01-ui-elements-remove/tsconfig.json b/examples/02-ui-components/01-ui-elements-remove/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/01-ui-elements-remove/vite.config.ts b/examples/02-ui-components/01-ui-elements-remove/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/01-ui-elements-remove/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/.bnexample.json b/examples/02-ui-components/02-formatting-toolbar-buttons/.bnexample.json deleted file mode 100644 index 5663d51f0b..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Intermediate", "Inline Content", "UI Components", "Formatting Toolbar"] -} diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx deleted file mode 100644 index 92c4b48073..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/App.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { - BasicTextStyleButton, - BlockTypeSelect, - ColorStyleButton, - CreateLinkButton, - FileCaptionButton, - FileReplaceButton, - FormattingToolbar, - FormattingToolbarController, - NestBlockButton, - TextAlignButton, - UnnestBlockButton, - useCreateBlockNote, -} from "@blocknote/react"; - -import { BlueButton } from "./BlueButton"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: [ - { - type: "text", - text: "You can now toggle ", - styles: {}, - }, - { - type: "text", - text: "blue", - styles: { textColor: "blue", backgroundColor: "blue" }, - }, - { - type: "text", - text: " and ", - styles: {}, - }, - { - type: "text", - text: "code", - styles: { code: true }, - }, - { - type: "text", - text: " styles with new buttons in the Formatting Toolbar", - styles: {}, - }, - ], - }, - { - type: "paragraph", - content: "Select some text to try them out", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - ( - - - - {/* Extra button to toggle blue text & background */} - - - - - - - - - - {/* Extra button to toggle code styles */} - - - - - - - - - - - - - - )} - /> - - ); -} diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/BlueButton.tsx b/examples/02-ui-components/02-formatting-toolbar-buttons/BlueButton.tsx deleted file mode 100644 index 48400a273e..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/BlueButton.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { - useBlockNoteEditor, - useComponentsContext, - useEditorContentOrSelectionChange, -} from "@blocknote/react"; -import "@blocknote/mantine/style.css"; -import { useState } from "react"; - -// Custom Formatting Toolbar Button to toggle blue text & background color. -export function BlueButton() { - const editor = useBlockNoteEditor(); - - const Components = useComponentsContext()!; - - // Tracks whether the text & background are both blue. - const [isSelected, setIsSelected] = useState( - editor.getActiveStyles().textColor === "blue" && - editor.getActiveStyles().backgroundColor === "blue" - ); - - // Updates state on content or selection change. - useEditorContentOrSelectionChange(() => { - setIsSelected( - editor.getActiveStyles().textColor === "blue" && - editor.getActiveStyles().backgroundColor === "blue" - ); - }, editor); - - return ( - { - editor.toggleStyles({ - textColor: "blue", - backgroundColor: "blue", - }); - }} - isSelected={isSelected}> - Blue - - ); -} diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/README.md b/examples/02-ui-components/02-formatting-toolbar-buttons/README.md deleted file mode 100644 index 8287076113..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Adding Formatting Toolbar Buttons - -In this example, we add a blue text/background color and code style button to the Formatting Toolbar. - -**Try it out:** Select some text to open the Formatting Toolbar, and click one of the new buttons! - -**Relevant Docs:** - -- [Changing the Formatting Toolbar](/docs/ui-components/formatting-toolbar#changing-the-formatting-toolbar) -- [Manipulating Inline Content](/docs/editor-api/manipulating-inline-content) -- [Hooks](TODO) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/index.html b/examples/02-ui-components/02-formatting-toolbar-buttons/index.html deleted file mode 100644 index 054b18c47d..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding Formatting Toolbar Buttons - - -
- - - diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/main.tsx b/examples/02-ui-components/02-formatting-toolbar-buttons/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/package.json b/examples/02-ui-components/02-formatting-toolbar-buttons/package.json deleted file mode 100644 index 86c2554b4a..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-formatting-toolbar-buttons", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/tsconfig.json b/examples/02-ui-components/02-formatting-toolbar-buttons/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/02-formatting-toolbar-buttons/vite.config.ts b/examples/02-ui-components/02-formatting-toolbar-buttons/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/02-formatting-toolbar-buttons/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/.bnexample.json b/examples/02-ui-components/03-formatting-toolbar-block-type-items/.bnexample.json deleted file mode 100644 index 1595fd3785..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/.bnexample.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Intermediate", "Blocks", "UI Components", "Formatting Toolbar", "Custom Schemas"], - "dependencies": { - "@mantine/core": "^7.10.1", - "react-icons": "^5.2.1" - } -} diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx b/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx deleted file mode 100644 index 44d1d5d69d..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { defaultProps } from "@blocknote/core"; -import { createReactBlockSpec } from "@blocknote/react"; -import { Menu } from "@mantine/core"; -import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; - -import "./styles.css"; - -// The types of alerts that users can choose from. -export const alertTypes = [ - { - title: "Warning", - value: "warning", - icon: MdError, - color: "#e69819", - backgroundColor: { - light: "#fff6e6", - dark: "#805d20", - }, - }, - { - title: "Error", - value: "error", - icon: MdCancel, - color: "#d80d0d", - backgroundColor: { - light: "#ffe6e6", - dark: "#802020", - }, - }, - { - title: "Info", - value: "info", - icon: MdInfo, - color: "#507aff", - backgroundColor: { - light: "#e6ebff", - dark: "#203380", - }, - }, - { - title: "Success", - value: "success", - icon: MdCheckCircle, - color: "#0bc10b", - backgroundColor: { - light: "#e6ffe6", - dark: "#208020", - }, - }, -] as const; - -// The Alert block. -export const Alert = createReactBlockSpec( - { - type: "alert", - propSchema: { - textAlignment: defaultProps.textAlignment, - textColor: defaultProps.textColor, - type: { - default: "warning", - values: ["warning", "error", "info", "success"], - }, - }, - content: "inline", - }, - { - render: (props) => { - const alertType = alertTypes.find( - (a) => a.value === props.block.props.type - )!; - const Icon = alertType.icon; - - return ( -
- {/*Icon which opens a menu to choose the Alert type*/} - - -
- -
-
- {/*Dropdown to change the Alert type*/} - - Alert Type - - {alertTypes.map((type) => { - const ItemIcon = type.icon; - - return ( - - } - onClick={() => - props.editor.updateBlock(props.block, { - type: "alert", - props: { type: type.value }, - }) - }> - {type.title} - - ); - })} - -
- {/*Rich text field for user to type in*/} -
-
- ); - }, - } -); diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx b/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx deleted file mode 100644 index fce0767f09..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/App.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; -import "@blocknote/core/fonts/inter.css"; -import { - BlockTypeSelectItem, - FormattingToolbar, - FormattingToolbarController, - blockTypeSelectItems, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { RiAlertFill } from "react-icons/ri"; - -import { Alert } from "./Alert"; - -// Our schema with block specs, which contain the configs and implementations -// for blocks that we want our editor to use. -const schema = BlockNoteSchema.create({ - blockSpecs: { - // Adds all default blocks. - ...defaultBlockSpecs, - // Adds the Alert block. - alert: Alert, - }, -}); - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - schema, - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: - "Try selecting some text - you'll see the new 'Alert' item in the Block Type Select", - }, - { - type: "alert", - content: - "Or select text in this alert - the Block Type Select also appears", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance with the updated Block Type Select. - return ( - - ( - block.type === "alert", - } satisfies BlockTypeSelectItem, - ]} - /> - )} - /> - - ); -} diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md b/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md deleted file mode 100644 index 005e3bc916..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Adding Block Type Select Items - -In this example, we add an item to the Block Type Select, so that it works for a custom alert block we create. - -**Try it out:** Select some text to open the Formatting Toolbar, and click "Alert" in the Block Type Select to change the selected block! - -**Relevant Docs:** - -- [Changing Block Type Select Items](/docs/ui-components/formatting-toolbar#changing-block-type-select-items) -- [Custom Block Types](/docs/custom-schemas/custom-blocks) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html b/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html deleted file mode 100644 index 1e84936eca..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding Block Type Select Items - - -
- - - diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/main.tsx b/examples/02-ui-components/03-formatting-toolbar-block-type-items/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/package.json b/examples/02-ui-components/03-formatting-toolbar-block-type-items/package.json deleted file mode 100644 index 0f75c1cf1a..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@blocknote/example-formatting-toolbar-block-type-items", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "@mantine/core": "^7.10.1", - "react-icons": "^5.2.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/styles.css b/examples/02-ui-components/03-formatting-toolbar-block-type-items/styles.css deleted file mode 100644 index 7296b28dce..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/styles.css +++ /dev/null @@ -1,74 +0,0 @@ -.alert { - display: flex; - justify-content: center; - align-items: center; - flex-grow: 1; - border-radius: 4px; - min-height: 48px; - padding: 4px; -} - -.alert[data-alert-type="warning"] { - background-color: #fff6e6; -} - -.alert[data-alert-type="error"] { - background-color: #ffe6e6; -} - -.alert[data-alert-type="info"] { - background-color: #e6ebff; -} - -.alert[data-alert-type="success"] { - background-color: #e6ffe6; -} - -[data-color-scheme="dark"] .alert[data-alert-type="warning"] { - background-color: #805d20; -} - -[data-color-scheme="dark"] .alert[data-alert-type="error"] { - background-color: #802020; -} - -[data-color-scheme="dark"] .alert[data-alert-type="info"] { - background-color: #203380; -} - -[data-color-scheme="dark"] .alert[data-alert-type="success"] { - background-color: #208020; -} - -.alert-icon-wrapper { - border-radius: 16px; - display: flex; - justify-content: center; - align-items: center; - margin-left: 12px; - margin-right: 12px; - height: 18px; - width: 18px; - user-select: none; - cursor: pointer; -} - -.alert-icon[data-alert-icon-type="warning"] { - color: #e69819 -} - -.alert-icon[data-alert-icon-type="error"] { - color: #d80d0d -} - -.alert-icon[data-alert-icon-type="info"] { - color: #507aff -} - -.alert-icon[data-alert-icon-type="success"] { - color: #0bc10b -} - -.inline-content { - flex-grow: 1; -} \ No newline at end of file diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json b/examples/02-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts b/examples/02-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/04-side-menu-buttons/.bnexample.json b/examples/02-ui-components/04-side-menu-buttons/.bnexample.json deleted file mode 100644 index 90cd5e6f3b..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/.bnexample.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Intermediate", "Blocks", "UI Components", "Block Side Menu"], - "dependencies": { - "react-icons": "^5.2.1" - } -} diff --git a/examples/02-ui-components/04-side-menu-buttons/App.tsx b/examples/02-ui-components/04-side-menu-buttons/App.tsx deleted file mode 100644 index 446d3e14c5..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/App.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { - DragHandleButton, - SideMenu, - SideMenuController, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import { RemoveBlockButton } from "./RemoveBlockButton"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "<- Notice the new button in the side menu", - }, - { - type: "paragraph", - content: "Click it to remove the hovered block", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - ( - - {/* Button which removes the hovered block. */} - - - - )} - /> - - ); -} diff --git a/examples/02-ui-components/04-side-menu-buttons/README.md b/examples/02-ui-components/04-side-menu-buttons/README.md deleted file mode 100644 index 63c89aa1ec..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Adding Block Side Menu Buttons - -In this example, we replace the button to add a block in the Block Side Menu, with a button to remove the hovered block. - -**Try it out:** Hover a block to open the Block Side Menu, and click the new button! - -**Relevant Docs:** - -- [Changing the Block Side Menu](/docs/ui-components/side-menu#changing-the-block-side-menu) -- [Removing Blocks](/docs/editor-api/manipulating-blocks#removing-blocks) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx b/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx deleted file mode 100644 index c387f67f1e..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/RemoveBlockButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { - SideMenuProps, - useBlockNoteEditor, - useComponentsContext, -} from "@blocknote/react"; -import { MdDelete } from "react-icons/md"; - -// Custom Side Menu button to remove the hovered block. -export function RemoveBlockButton(props: SideMenuProps) { - const editor = useBlockNoteEditor(); - - const Components = useComponentsContext()!; - - return ( - { - editor.removeBlocks([props.block]); - }} - /> - } - /> - ); -} diff --git a/examples/02-ui-components/04-side-menu-buttons/index.html b/examples/02-ui-components/04-side-menu-buttons/index.html deleted file mode 100644 index 5417d76adf..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding Block Side Menu Buttons - - -
- - - diff --git a/examples/02-ui-components/04-side-menu-buttons/main.tsx b/examples/02-ui-components/04-side-menu-buttons/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/04-side-menu-buttons/package.json b/examples/02-ui-components/04-side-menu-buttons/package.json deleted file mode 100644 index 95eca7033f..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@blocknote/example-side-menu-buttons", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-icons": "^5.2.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/04-side-menu-buttons/tsconfig.json b/examples/02-ui-components/04-side-menu-buttons/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/04-side-menu-buttons/vite.config.ts b/examples/02-ui-components/04-side-menu-buttons/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/04-side-menu-buttons/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/.bnexample.json b/examples/02-ui-components/05-side-menu-drag-handle-items/.bnexample.json deleted file mode 100644 index 90cd5e6f3b..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/.bnexample.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Intermediate", "Blocks", "UI Components", "Block Side Menu"], - "dependencies": { - "react-icons": "^5.2.1" - } -} diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/App.tsx b/examples/02-ui-components/05-side-menu-drag-handle-items/App.tsx deleted file mode 100644 index 5050132bbc..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/App.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { - BlockColorsItem, - DragHandleMenu, - RemoveBlockItem, - SideMenu, - SideMenuController, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import { ResetBlockTypeItem } from "./ResetBlockTypeItem"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "<- Click the Drag Handle to see the new item", - }, - { - type: "bulletListItem", - content: - "Try resetting this block's type using the new Drag Handle Menu item", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - ( - ( - - Delete - Colors - {/* Item which resets the hovered block's type. */} - Reset Type - - )} - /> - )} - /> - - ); -} diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/README.md b/examples/02-ui-components/05-side-menu-drag-handle-items/README.md deleted file mode 100644 index f57b2bb495..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Adding Drag Handle Menu Items - -In this example, we add an item to the Drag Handle Menu, which resets the hovered block to a paragraph. - -**Try it out:** Hover a block to open the Block Side Menu, and click "Reset Type" in the Drag Handle Menu to reset the selected block! - -**Relevant Docs:** - -- [Changing Drag Handle Menu Items](/docs/ui-components/side-menu#changing-drag-handle-menu-items) -- [Updating Blocks](/docs/editor-api/manipulating-blocks#updating-blocks) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/ResetBlockTypeItem.tsx b/examples/02-ui-components/05-side-menu-drag-handle-items/ResetBlockTypeItem.tsx deleted file mode 100644 index e28ce16da0..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/ResetBlockTypeItem.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { - DragHandleMenuProps, - useBlockNoteEditor, - useComponentsContext, -} from "@blocknote/react"; - -export function ResetBlockTypeItem(props: DragHandleMenuProps) { - const editor = useBlockNoteEditor(); - - const Components = useComponentsContext()!; - - return ( - { - editor.updateBlock(props.block, { type: "paragraph" }); - }}> - Reset Type - - ); -} diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/index.html b/examples/02-ui-components/05-side-menu-drag-handle-items/index.html deleted file mode 100644 index f0470fd6a4..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding Drag Handle Menu Items - - -
- - - diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/main.tsx b/examples/02-ui-components/05-side-menu-drag-handle-items/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/package.json b/examples/02-ui-components/05-side-menu-drag-handle-items/package.json deleted file mode 100644 index 1b026e382e..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@blocknote/example-side-menu-drag-handle-items", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-icons": "^5.2.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/tsconfig.json b/examples/02-ui-components/05-side-menu-drag-handle-items/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/05-side-menu-drag-handle-items/vite.config.ts b/examples/02-ui-components/05-side-menu-drag-handle-items/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/05-side-menu-drag-handle-items/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/.bnexample.json b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/.bnexample.json deleted file mode 100644 index ecb51384b1..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/.bnexample.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "yousefed", - "tags": [ - "Intermediate", - "Blocks", - "UI Components", - "Suggestion Menus", - "Slash Menu" - ], - "dependencies": { - "react-icons": "^5.2.1" - } -} diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/App.tsx b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/App.tsx deleted file mode 100644 index 984cf3d4e1..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/App.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { - BlockNoteEditor, - filterSuggestionItems, - PartialBlock, -} from "@blocknote/core"; -import "@blocknote/core/fonts/inter.css"; -import { - DefaultReactSuggestionItem, - getDefaultReactSlashMenuItems, - SuggestionMenuController, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { HiOutlineGlobeAlt } from "react-icons/hi"; - -// Custom Slash Menu item to insert a block after the current one. -const insertHelloWorldItem = (editor: BlockNoteEditor) => ({ - title: "Insert Hello World", - onItemClick: () => { - // Block that the text cursor is currently in. - const currentBlock = editor.getTextCursorPosition().block; - - // New block we want to insert. - const helloWorldBlock: PartialBlock = { - type: "paragraph", - content: [{ type: "text", text: "Hello World", styles: { bold: true } }], - }; - - // Inserting the new block after the current one. - editor.insertBlocks([helloWorldBlock], currentBlock, "after"); - }, - aliases: ["helloworld", "hw"], - group: "Other", - icon: , - subtext: "Used to insert a block with 'Hello World' below.", -}); - -// List containing all default Slash Menu Items, as well as our custom one. -const getCustomSlashMenuItems = ( - editor: BlockNoteEditor -): DefaultReactSuggestionItem[] => [ - ...getDefaultReactSlashMenuItems(editor), - insertHelloWorldItem(editor), -]; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Press the '/' key to open the Slash Menu", - }, - { - type: "paragraph", - content: "Notice the new 'Insert Hello World' item - try it out!", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - - filterSuggestionItems(getCustomSlashMenuItems(editor), query) - } - /> - - ); -} diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/README.md b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/README.md deleted file mode 100644 index f525b3de8e..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Adding Slash Menu Items - -In this example, we add an item to the Slash Menu, which adds a new block below with a bold "Hello World" string. - -**Try it out:** Press the "/" key to open the Slash Menu and select the new item! - -**Relevant Docs:** - -- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items) -- [Getting Text Cursor Position](/docs/editor-api/cursor-selections#getting-text-cursor-position) -- [Inserting New Blocks](/docs/editor-api/manipulating-blocks#inserting-new-blocks) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/index.html b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/index.html deleted file mode 100644 index 3724c202ed..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding Slash Menu Items - - -
- - - diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/main.tsx b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/package.json b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/package.json deleted file mode 100644 index 424160f0e8..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@blocknote/example-suggestion-menus-slash-menu-items", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-icons": "^5.2.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts b/examples/02-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/App.tsx b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/App.tsx deleted file mode 100644 index 2bd26465f0..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/App.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { - DefaultReactSuggestionItem, - SuggestionMenuController, - SuggestionMenuProps, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import "./styles.css"; - -// Custom component to replace the default Slash Menu. -function CustomSlashMenu( - props: SuggestionMenuProps -) { - return ( -
- {props.items.map((item, index) => ( -
{ - props.onItemClick?.(item); - }}> - {item.title} -
- ))} -
- ); -} - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Press the '/' key to open the Slash Menu", - }, - { - type: "paragraph", - content: "It's been replaced with a custom component", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - - - ); -} diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/README.md b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/README.md deleted file mode 100644 index 99387c4ee3..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Replacing Slash Menu Component - -In this example, we replace the default Slash Menu component with a basic custom one. - -**Try it out:** Press the "/" key to see the new Slash Menu! - -**Relevant Docs:** - -- [Replacing the Slash Menu Component](/docs/ui-components/suggestion-menus#replacing-the-suggestion-menu-component) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/index.html b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/index.html deleted file mode 100644 index a2c63152a7..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Replacing Slash Menu Component - - -
- - - diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/main.tsx b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/package.json b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/package.json deleted file mode 100644 index 82d0c70c2c..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-suggestion-menus-slash-menu-component", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/styles.css b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/styles.css deleted file mode 100644 index 61a6e95323..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/styles.css +++ /dev/null @@ -1,37 +0,0 @@ -.slash-menu { - z-index: 9999; - - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 8px #dddddd; - - display: flex; - flex-direction: column; - gap: 8px; - - padding: 8px; - - top: 8px; -} - -.slash-menu-item { - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - cursor: pointer; - - font-size: 16px; - - align-items: center; - display: flex; - flex-direction: row; - - padding: 8px; -} - -.slash-menu-item:hover, .slash-menu-item.selected { - background-color: lightgray; -} \ No newline at end of file diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts b/examples/02-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/08-custom-ui/.bnexample.json b/examples/02-ui-components/08-custom-ui/.bnexample.json deleted file mode 100644 index fa3c6e369b..0000000000 --- a/examples/02-ui-components/08-custom-ui/.bnexample.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Advanced", "Inline Content", "UI Components", "Block Side Menu", "Formatting Toolbar", "Suggestion Menus", "Slash Menu", "Appearance & Styling"], - "dependencies": { - "react-icons": "^5.2.1" - } -} diff --git a/examples/02-ui-components/08-custom-ui/App.tsx b/examples/02-ui-components/08-custom-ui/App.tsx deleted file mode 100644 index f3bb61a09f..0000000000 --- a/examples/02-ui-components/08-custom-ui/App.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { filterSuggestionItems } from "@blocknote/core"; -import "@blocknote/core/fonts/inter.css"; -import { - getDefaultReactSlashMenuItems, - SideMenuController, - SuggestionMenuController, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import { CustomFormattingToolbar } from "./CustomFormattingToolbar"; -import { CustomSideMenu } from "./CustomSideMenu"; -import { CustomSlashMenu } from "./CustomSlashMenu"; -import "./styles.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - {/* Adds the custom Formatting Toolbar */} - {/* `FormattingToolbarController isn't used since the custom toolbar is - static and always visible above the editor. */} - - {/* Adds the custom Side Menu and Slash Menu. */} - {/* These use controllers since we want them to be positioned and - show/hide the same as the default ones.*/} - - - filterSuggestionItems(getDefaultReactSlashMenuItems(editor), query) - } - suggestionMenuComponent={CustomSlashMenu} - onItemClick={(i) => i.onItemClick()} - /> - - ); -} diff --git a/examples/02-ui-components/08-custom-ui/ColorMenu.tsx b/examples/02-ui-components/08-custom-ui/ColorMenu.tsx deleted file mode 100644 index 583c84d6a9..0000000000 --- a/examples/02-ui-components/08-custom-ui/ColorMenu.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { - useBlockNoteEditor, - useEditorChange, - useEditorSelectionChange, -} from "@blocknote/react"; -import { useState } from "react"; -import { MdFormatColorText } from "react-icons/md"; - -export const colors = [ - "default", - "red", - "orange", - "yellow", - "green", - "blue", - "purple", -] as const; - -// Formatting Toolbar sub menu for changing text and background color. -export function ColorMenu(props: { className?: string }) { - const editor = useBlockNoteEditor(); - - // Colors of the currently selected text. - const [textColor, setTextColor] = useState( - (editor.getActiveStyles().textColor as string) || "default" - ); - const [backgroundColor, setCurrentColor] = useState( - (editor.getActiveStyles().backgroundColor as string) || "default" - ); - - // Updates the colors when the editor content or selection changes. - useEditorChange(() => { - setTextColor((editor.getActiveStyles().textColor as string) || "default"); - setCurrentColor( - (editor.getActiveStyles().backgroundColor as string) || "default" - ); - }, editor); - useEditorSelectionChange(() => { - setTextColor((editor.getActiveStyles().textColor as string) || "default"); - setCurrentColor( - (editor.getActiveStyles().backgroundColor as string) || "default" - ); - }, editor); - - return ( -
- {/* Group for text color buttons */} -
- {colors.map((color) => ( - // Button for each color - - ))} -
- {/* Group for background color buttons */} -
- {colors.map((color) => ( - // Button for each color - - ))} -
-
- ); -} diff --git a/examples/02-ui-components/08-custom-ui/CustomFormattingToolbar.tsx b/examples/02-ui-components/08-custom-ui/CustomFormattingToolbar.tsx deleted file mode 100644 index 04a4673512..0000000000 --- a/examples/02-ui-components/08-custom-ui/CustomFormattingToolbar.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { - useBlockNoteEditor, - useEditorContentOrSelectionChange, -} from "@blocknote/react"; -import { useState } from "react"; -import { - MdAddLink, - MdFormatAlignCenter, - MdFormatAlignJustify, - MdFormatAlignLeft, - MdFormatAlignRight, - MdFormatBold, - MdFormatColorText, - MdFormatItalic, - MdFormatUnderlined, -} from "react-icons/md"; - -import { checkBlockHasDefaultProp } from "@blocknote/core"; -import { ColorMenu } from "./ColorMenu"; -import { LinkMenu } from "./LinkMenu"; - -type CustomFormattingToolbarState = { - bold: boolean; - italic: boolean; - underline: boolean; - - textAlignment: "left" | "center" | "right" | "justify" | undefined; - - textColor: string; - backgroundColor: string; -}; - -// Custom component to replace the default Formatting Toolbar. -export function CustomFormattingToolbar() { - const editor = useBlockNoteEditor(); - - // Function to get the state of toolbar buttons (active/inactive). - // TODO: this is a bit weird, better to use useSelectedBlocks and useActiveStyles hooks - const getState = (): CustomFormattingToolbarState => { - const block = editor.getTextCursorPosition().block; - const activeStyles = editor.getActiveStyles(); - - return { - bold: (activeStyles.bold as boolean) || false, - italic: (activeStyles.italic as boolean) || false, - underline: (activeStyles.underline as boolean) || false, - - textAlignment: checkBlockHasDefaultProp("textAlignment", block, editor) - ? block.props.textAlignment - : undefined, - - textColor: (activeStyles.textColor as string) || "default", - backgroundColor: (activeStyles.backgroundColor as string) || "default", - }; - }; - - // Callback to set text alignment. - const setTextAlignment = ( - textAlignment: CustomFormattingToolbarState["textAlignment"] - ) => { - const selection = editor.getSelection(); - - if (selection) { - for (const block of selection.blocks) { - editor.updateBlock(block, { - props: { textAlignment: textAlignment }, - }); - } - } else { - const block = editor.getTextCursorPosition().block; - - editor.updateBlock(block, { - props: { textAlignment: textAlignment }, - }); - } - }; - - // Keeps track of the state of toolbar buttons. - const [state, setState] = useState(getState()); - - // Keeps track of if the color and link sub menus are open. - const [colorMenuOpen, setColorMenuOpen] = useState(false); - const [linkMenuOpen, setLinkMenuOpen] = useState(false); - - // Updates toolbar state when the editor content or selection changes. - useEditorContentOrSelectionChange(() => setState(getState()), editor); - - return ( -
- {/* Button group for toggled text styles. */} -
- {/* Toggle bold button */} - - {/* Toggle italic button */} - - {/* Toggle underline button */} - -
- {/* Button group for text alignment */} - {state.textAlignment && ( -
- {/*Left align button*/} - - {/* Center align button */} - - {/* Right align button */} - - {/* Justify text button */} - -
- )} - {/* Button group for color menu */} -
-
- - -
-
- {/* Button group for link menu */} -
-
- - -
-
-
- ); -} diff --git a/examples/02-ui-components/08-custom-ui/CustomSideMenu.tsx b/examples/02-ui-components/08-custom-ui/CustomSideMenu.tsx deleted file mode 100644 index 85ec869f5c..0000000000 --- a/examples/02-ui-components/08-custom-ui/CustomSideMenu.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { SideMenuProps } from "@blocknote/react"; -import { RxDragHandleHorizontal } from "react-icons/rx"; - -// Custom component to replace the default Block Side Menu. -export function CustomSideMenu(props: SideMenuProps) { - return ( -
- -
- ); -} diff --git a/examples/02-ui-components/08-custom-ui/CustomSlashMenu.tsx b/examples/02-ui-components/08-custom-ui/CustomSlashMenu.tsx deleted file mode 100644 index e4061ef8bf..0000000000 --- a/examples/02-ui-components/08-custom-ui/CustomSlashMenu.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { - DefaultReactSuggestionItem, - SuggestionMenuProps, - useBlockNoteEditor, -} from "@blocknote/react"; - -// Custom component to replace the default Slash Menu. -export function CustomSlashMenu( - props: SuggestionMenuProps -) { - const editor = useBlockNoteEditor(); - - // Sorts items into their groups. - const groups: Record = {}; - for (const item of props.items) { - const group = item.group || item.title; - - if (!groups[group]) { - groups[group] = []; - } - - groups[group].push(item); - } - - // If query matches no items, shows "No matches" message. - if (props.items.length === 0) { - return
No matches
; - } - - return ( -
- {Object.entries(groups).map(([group, items]) => ( - // Component for each group -
- {/* Group label */} -
{group}
- {/* Group items */} -
- {items.map((item: DefaultReactSuggestionItem) => { - const Icon = item.icon; - return ( - - ); - })} -
-
- ))} -
- ); -} diff --git a/examples/02-ui-components/08-custom-ui/LinkMenu.tsx b/examples/02-ui-components/08-custom-ui/LinkMenu.tsx deleted file mode 100644 index 5f0631b96d..0000000000 --- a/examples/02-ui-components/08-custom-ui/LinkMenu.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { HTMLAttributes, useState } from "react"; -import { BlockNoteEditor } from "@blocknote/core"; - -// Formatting Toolbar sub menu for creating links. -export const LinkMenu = ( - props: { editor: BlockNoteEditor } & HTMLAttributes -) => { - const { editor, className, ...rest } = props; - - const [text, setText] = useState(""); - const [url, setUrl] = useState(""); - - return ( -
- {/*Input for link text*/} - setText(event.target.value)} - /> - {/*Input for link URL*/} - setUrl(event.target.value)} - /> - {/*Buttons to create and clear the inputs*/} -
- - -
-
- ); -}; diff --git a/examples/02-ui-components/08-custom-ui/README.md b/examples/02-ui-components/08-custom-ui/README.md deleted file mode 100644 index 3dc19a4d2d..0000000000 --- a/examples/02-ui-components/08-custom-ui/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Custom UI - -In this example, we replace the default Formatting Toolbar using a custom React component, as well as the default Slash Menu and Side Menu. The Formatting Toolbar is also made static and always visible above the editor. - -**Relevant Docs:** - -- [Formatting Toolbar](/docs/ui-components/formatting-toolbar) -- [Manipulating Inline Content](/docs/editor-api/manipulating-inline-content) -- [Hooks](TODO) -- [Slash Menu](/docs/ui-components/suggestion-menus#slash-menu) -- [Side Menu](/docs/ui-components/side-menu) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/08-custom-ui/index.html b/examples/02-ui-components/08-custom-ui/index.html deleted file mode 100644 index e306e7cb45..0000000000 --- a/examples/02-ui-components/08-custom-ui/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Custom UI - - -
- - - diff --git a/examples/02-ui-components/08-custom-ui/main.tsx b/examples/02-ui-components/08-custom-ui/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/08-custom-ui/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/08-custom-ui/package.json b/examples/02-ui-components/08-custom-ui/package.json deleted file mode 100644 index a29e9e7d82..0000000000 --- a/examples/02-ui-components/08-custom-ui/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@blocknote/example-custom-ui", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-icons": "^5.2.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/08-custom-ui/styles.css b/examples/02-ui-components/08-custom-ui/styles.css deleted file mode 100644 index 294d9864e0..0000000000 --- a/examples/02-ui-components/08-custom-ui/styles.css +++ /dev/null @@ -1,277 +0,0 @@ -.bn-container * { - font-family: "Inter", "SF Pro Display", -apple-system, BlinkMacSystemFont, - "Open Sans", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", - "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -} - -.color-menu { - position: absolute; - z-index: 9999; - - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - - display: flex; - flex-direction: column; - gap: 4px; - - margin-top: 8px; - padding: 4px; -} - -.color-menu-group { - display: flex; - flex-direction: row; - gap: 4px; -} - -.color-menu-item { - position: relative; - - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - align-items: center; - display: flex; - flex-direction: row; - gap: 4px; - justify-content: center; - - padding: 0; - - height: 24px; - width: 24px; -} - -.text.red { - color: #e03e3e; -} - -.text.orange { - color: #d9730d; -} - -.text.yellow { - color: #dfab01; -} - -.text.green { - color: #4d6461; -} - -.text.blue { - color: #0b6e99; -} - -.text.purple { - color: #6940a5; -} - -.background.red { - background-color: #fbe4e4; -} - -.background.orange { - background-color: #f6e9d9; -} - -.background.yellow { - background-color: #fbf3db; -} - -.background.green { - background-color: #ddedea; -} - -.background.blue { - background-color: #ddebf1; -} - -.background.purple { - background-color: #eae4f2; -} - -.color-menu-item.text:hover { - background-color: lightgray; -} - -.color-menu-item.background:hover { - color: gray; -} - -.link-menu { - position: absolute; - z-index: 9999; - - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - - display: flex; - flex-direction: column; - gap: 4px; - - margin-top: 8px; - padding: 4px; -} - -.link-input { - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - padding: 4px; -} - -.link-buttons { - display: flex; - flex-direction: row; - gap: 4px; -} - -.link-button { - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - cursor: pointer; - - font-size: 14px; - - flex-grow: 1; -} - -.link-button:hover { - background-color: lightgray; -} - -.formatting-toolbar { - position: sticky; - z-index: 9999; - - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 8px #dddddd; - - display: flex; - flex-direction: row; - gap: 16px; - - margin-inline: 54px; - margin-bottom: 8px; - padding: 4px; - - top: 8px; -} - -.formatting-toolbar-group { - display: flex; - flex-direction: row; - gap: 4px; -} - -.formatting-toolbar-button { - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - cursor: pointer; - - font-size: 16px; - - align-items: center; - display: flex; - justify-content: center; - - height: 32px; - width: 32px; -} - -.formatting-toolbar-button:hover { - background-color: lightgray; -} - -.slash-menu { - z-index: 9999; - - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 8px #dddddd; - - display: flex; - flex-direction: column; - gap: 8px; - - padding: 8px; - - top: 8px; -} - -.slash-menu-group { - display: flex; - flex-direction: column; - gap: 8px; -} - -.slash-menu-label { - color: gray; - font-size: 12px; -} - -.slash-menu-item-group { - display: flex; - flex-direction: row; - gap: 4px; -} - -.slash-menu-item { - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - cursor: pointer; - - font-size: 16px; - - align-items: center; - display: flex; - flex-direction: row; - - padding: 8px; -} - -.slash-menu-item:hover { - background-color: lightgray; -} - -.side-menu { - background-color: white; - border: 1px solid lightgray; - border-radius: 2px; - box-shadow: 0 0 4px #dddddd; - - cursor: pointer; - - align-items: center; - display: flex; - justify-content: center; - - margin-right: 4px; - padding: 8px; -} - -.active { - box-shadow: inset 0 0 6px #cccccc; -} - -.hidden { - display: none; -} \ No newline at end of file diff --git a/examples/02-ui-components/08-custom-ui/tsconfig.json b/examples/02-ui-components/08-custom-ui/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/08-custom-ui/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/08-custom-ui/vite.config.ts b/examples/02-ui-components/08-custom-ui/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/08-custom-ui/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/link-toolbar-buttons/App.tsx b/examples/02-ui-components/link-toolbar-buttons/App.tsx deleted file mode 100644 index 4664d4bf28..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/App.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { - LinkToolbar, - LinkToolbarController, - useCreateBlockNote, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import { AlertButton } from "./AlertButton"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Hover the link below to see the modified Link Toolbar", - }, - { - type: "paragraph", - content: [ - { - type: "link", - href: "https://www.blocknotejs.org/", - content: [ - { - type: "text", - text: "Home Page", - styles: {}, - }, - ], - }, - ], - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance. - return ( - - ( - - - - )} - /> - - ); -} diff --git a/examples/02-ui-components/link-toolbar-buttons/README.md b/examples/02-ui-components/link-toolbar-buttons/README.md deleted file mode 100644 index 2257f47059..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Adding Link Toolbar Buttons - -In this example, we add a button to the Link Toolbar which opens a browser alert. - -**Try it out:** Hover the link open the Link Toolbar, and click the new "Open Alert" button! - -**Relevant Docs:** - -- [Changing the Link Toolbar](/docs/ui-components/link-toolbar#changing-the-link-toolbar) -- [Editor Setup](/docs/editor-basics/setup) \ No newline at end of file diff --git a/examples/02-ui-components/link-toolbar-buttons/index.html b/examples/02-ui-components/link-toolbar-buttons/index.html deleted file mode 100644 index c7356c7253..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding Link Toolbar Buttons - - -
- - - diff --git a/examples/02-ui-components/link-toolbar-buttons/main.tsx b/examples/02-ui-components/link-toolbar-buttons/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/02-ui-components/link-toolbar-buttons/package.json b/examples/02-ui-components/link-toolbar-buttons/package.json deleted file mode 100644 index 2002403a7a..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-link-toolbar-buttons", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/link-toolbar-buttons/tsconfig.json b/examples/02-ui-components/link-toolbar-buttons/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/02-ui-components/link-toolbar-buttons/vite.config.ts b/examples/02-ui-components/link-toolbar-buttons/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/02-ui-components/link-toolbar-buttons/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/03-theming/01-theming-dom-attributes/App.tsx b/examples/03-theming/01-theming-dom-attributes/App.tsx deleted file mode 100644 index 7068e12388..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/App.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import "./styles.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - // Sets attributes on DOM elements in the editor. - domAttributes: { - // Adds a class to all `blockContainer` elements. - block: { - class: "hello-world-block", - }, - }, - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "You can see there's a border around each block", - }, - { - type: "paragraph", - content: [ - { - type: "text", - text: "This is because there's a CSS rule using the ", - styles: {}, - }, - { - type: "text", - text: "hello-world-block", - styles: { code: true }, - }, - { - type: "text", - text: " class we added", - styles: {}, - }, - ], - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance using a React component. - return ; -} diff --git a/examples/03-theming/01-theming-dom-attributes/README.md b/examples/03-theming/01-theming-dom-attributes/README.md deleted file mode 100644 index 535fc2d20f..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Adding CSS Class to Blocks - -In this example, we add a `hello-world-block` class to each block in the editor. We also create a CSS rule to add a border to all elements with that class. - -**Relevant Docs:** - -- [Adding DOM Attributes](/docs/styling-theming/adding-dom-attributes) \ No newline at end of file diff --git a/examples/03-theming/01-theming-dom-attributes/index.html b/examples/03-theming/01-theming-dom-attributes/index.html deleted file mode 100644 index 912cb1eca3..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Adding CSS Class to Blocks - - -
- - - diff --git a/examples/03-theming/01-theming-dom-attributes/main.tsx b/examples/03-theming/01-theming-dom-attributes/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/03-theming/01-theming-dom-attributes/package.json b/examples/03-theming/01-theming-dom-attributes/package.json deleted file mode 100644 index f87659a49b..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-theming-dom-attributes", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/03-theming/01-theming-dom-attributes/tsconfig.json b/examples/03-theming/01-theming-dom-attributes/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/03-theming/01-theming-dom-attributes/vite.config.ts b/examples/03-theming/01-theming-dom-attributes/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/03-theming/01-theming-dom-attributes/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/03-theming/02-changing-font/.bnexample.json b/examples/03-theming/02-changing-font/.bnexample.json deleted file mode 100644 index 365a51f81b..0000000000 --- a/examples/03-theming/02-changing-font/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Basic", "Appearance & Styling"] -} \ No newline at end of file diff --git a/examples/03-theming/02-changing-font/App.tsx b/examples/03-theming/02-changing-font/App.tsx deleted file mode 100644 index 4ef3f9a3dd..0000000000 --- a/examples/03-theming/02-changing-font/App.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import "./styles.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "You'll see that the font has been changed to Comic Sans MS", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance using a React component. - // Adds `data-changing-font-demo` to restrict styles to only this demo. - return ; -} diff --git a/examples/03-theming/02-changing-font/README.md b/examples/03-theming/02-changing-font/README.md deleted file mode 100644 index 2ed91a66fd..0000000000 --- a/examples/03-theming/02-changing-font/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Changing Editor Font - -In this example, we override some of the default editor CSS to change font within the editor. - -**Relevant Docs:** - -- [Overriding CSS](/docs/styling-theming/overriding-css) \ No newline at end of file diff --git a/examples/03-theming/02-changing-font/index.html b/examples/03-theming/02-changing-font/index.html deleted file mode 100644 index 7bdb6289a5..0000000000 --- a/examples/03-theming/02-changing-font/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Changing Editor Font - - -
- - - diff --git a/examples/03-theming/02-changing-font/main.tsx b/examples/03-theming/02-changing-font/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/03-theming/02-changing-font/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/03-theming/02-changing-font/package.json b/examples/03-theming/02-changing-font/package.json deleted file mode 100644 index 25943f06ba..0000000000 --- a/examples/03-theming/02-changing-font/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-changing-font", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/03-theming/02-changing-font/styles.css b/examples/03-theming/02-changing-font/styles.css deleted file mode 100644 index b5eabf064f..0000000000 --- a/examples/03-theming/02-changing-font/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.bn-container[data-changing-font-demo] .bn-editor * { - font-family: "Comic Sans MS", sans-serif; -} \ No newline at end of file diff --git a/examples/03-theming/02-changing-font/tsconfig.json b/examples/03-theming/02-changing-font/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/03-theming/02-changing-font/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/03-theming/02-changing-font/vite.config.ts b/examples/03-theming/02-changing-font/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/03-theming/02-changing-font/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/03-theming/03-theming-css/App.tsx b/examples/03-theming/03-theming-css/App.tsx deleted file mode 100644 index 493ae7d4ed..0000000000 --- a/examples/03-theming/03-theming-css/App.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import "./styles.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "You'll see that the text is now blue", - }, - { - type: "paragraph", - content: - "Press the '/' key - the hovered Slash Menu items are also blue", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance using a React component. - // Adds `data-theming-css-demo` to restrict styles to only this demo. - return ; -} diff --git a/examples/03-theming/03-theming-css/README.md b/examples/03-theming/03-theming-css/README.md deleted file mode 100644 index 8fa0c4d771..0000000000 --- a/examples/03-theming/03-theming-css/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Overriding CSS Styles - -In this example, we override some of the default editor CSS to make the editor text and hovered Slash Menu items blue. - -**Relevant Docs:** - -- [Overriding CSS](/docs/styling-theming/overriding-css) \ No newline at end of file diff --git a/examples/03-theming/03-theming-css/index.html b/examples/03-theming/03-theming-css/index.html deleted file mode 100644 index 79a251e0be..0000000000 --- a/examples/03-theming/03-theming-css/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Overriding CSS Styles - - -
- - - diff --git a/examples/03-theming/03-theming-css/main.tsx b/examples/03-theming/03-theming-css/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/03-theming/03-theming-css/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/03-theming/03-theming-css/package.json b/examples/03-theming/03-theming-css/package.json deleted file mode 100644 index 1825ea8761..0000000000 --- a/examples/03-theming/03-theming-css/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-theming-css", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/03-theming/03-theming-css/styles.css b/examples/03-theming/03-theming-css/styles.css deleted file mode 100644 index 8909238e5f..0000000000 --- a/examples/03-theming/03-theming-css/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -/* Adds border and shadow to editor */ -.bn-container[data-theming-css-demo] .bn-editor * { - color: blue; -} - -/* Makes slash menu hovered items blue */ -.bn-container[data-theming-css-demo] - .bn-suggestion-menu-item[aria-selected="true"], -.bn-container[data-theming-css-demo] .bn-suggestion-menu-item:hover { - background-color: blue; -} diff --git a/examples/03-theming/03-theming-css/tsconfig.json b/examples/03-theming/03-theming-css/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/03-theming/03-theming-css/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/03-theming/03-theming-css/vite.config.ts b/examples/03-theming/03-theming-css/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/03-theming/03-theming-css/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/03-theming/04-theming-css-variables/App.tsx b/examples/03-theming/04-theming-css-variables/App.tsx deleted file mode 100644 index ba45053f04..0000000000 --- a/examples/03-theming/04-theming-css-variables/App.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; - -import "./styles.css"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Open up a menu or toolbar to see more of the red theme", - }, - { - type: "paragraph", - content: - "Toggle light/dark mode in the page footer and see the theme change too", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance using a React component. - // Adds `data-theming-css-variables-demo` to restrict styles to only this demo. - return ; -} diff --git a/examples/03-theming/04-theming-css-variables/README.md b/examples/03-theming/04-theming-css-variables/README.md deleted file mode 100644 index e86319a161..0000000000 --- a/examples/03-theming/04-theming-css-variables/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Overriding Theme CSS Variables - -In this example, we override the editor's default theme CSS variables to create a red theme for both light and dark modes. - -**Relevant Docs:** - -- [Theme CSS Variables](/docs/styling-theming/themes#theme-css-variables) \ No newline at end of file diff --git a/examples/03-theming/04-theming-css-variables/index.html b/examples/03-theming/04-theming-css-variables/index.html deleted file mode 100644 index e130442a5c..0000000000 --- a/examples/03-theming/04-theming-css-variables/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Overriding Theme CSS Variables - - -
- - - diff --git a/examples/03-theming/04-theming-css-variables/main.tsx b/examples/03-theming/04-theming-css-variables/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/03-theming/04-theming-css-variables/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/03-theming/04-theming-css-variables/package.json b/examples/03-theming/04-theming-css-variables/package.json deleted file mode 100644 index a1e82871c9..0000000000 --- a/examples/03-theming/04-theming-css-variables/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-theming-css-variables", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/03-theming/04-theming-css-variables/styles.css b/examples/03-theming/04-theming-css-variables/styles.css deleted file mode 100644 index 59301c371b..0000000000 --- a/examples/03-theming/04-theming-css-variables/styles.css +++ /dev/null @@ -1,28 +0,0 @@ -/* Base theme */ -.bn-container[data-theming-css-variables-demo][data-color-scheme] { - --bn-colors-editor-text: #222222; - --bn-colors-editor-background: #ffeeee; - --bn-colors-menu-text: #ffffff; - --bn-colors-menu-background: #9b0000; - --bn-colors-tooltip-text: #ffffff; - --bn-colors-tooltip-background: #b00000; - --bn-colors-hovered-text: #ffffff; - --bn-colors-hovered-background: #b00000; - --bn-colors-selected-text: #ffffff; - --bn-colors-selected-background: #c50000; - --bn-colors-disabled-text: #9b0000; - --bn-colors-disabled-background: #7d0000; - --bn-colors-shadow: #640000; - --bn-colors-border: #870000; - --bn-colors-side-menu: #bababa; - --bn-color-highlight-colors: #ffffff; - --bn-border-radius: 4px; - --bn-font-family: Helvetica Neue, sans-serif; -} - -/* Changes for dark mode */ -.bn-container[data-theming-css-variables-demo][data-color-scheme="dark"] { - --bn-colors-editor-text: #ffffff; - --bn-colors-editor-background: #9b0000; - --bn-colors-side-menu: #ffffff; -} diff --git a/examples/03-theming/04-theming-css-variables/tsconfig.json b/examples/03-theming/04-theming-css-variables/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/03-theming/04-theming-css-variables/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/03-theming/04-theming-css-variables/vite.config.ts b/examples/03-theming/04-theming-css-variables/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/03-theming/04-theming-css-variables/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/03-theming/05-theming-css-variables-code/App.tsx b/examples/03-theming/05-theming-css-variables-code/App.tsx deleted file mode 100644 index 8a02496d7e..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/App.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { - BlockNoteView, - darkDefaultTheme, - lightDefaultTheme, - Theme, -} from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useCreateBlockNote } from "@blocknote/react"; - -// Base theme -const lightRedTheme = { - colors: { - editor: { - text: "#222222", - background: "#ffeeee", - }, - menu: { - text: "#ffffff", - background: "#9b0000", - }, - tooltip: { - text: "#ffffff", - background: "#b00000", - }, - hovered: { - text: "#ffffff", - background: "#b00000", - }, - selected: { - text: "#ffffff", - background: "#c50000", - }, - disabled: { - text: "#9b0000", - background: "#7d0000", - }, - shadow: "#640000", - border: "#870000", - sideMenu: "#bababa", - highlights: lightDefaultTheme.colors!.highlights, - }, - borderRadius: 4, - fontFamily: "Helvetica Neue, sans-serif", -} satisfies Theme; - -// The theme for dark mode, -// users the light theme defined above with a few changes -const darkRedTheme = { - ...lightRedTheme, - colors: { - ...lightRedTheme.colors, - editor: { - text: "#ffffff", - background: "#9b0000", - }, - sideMenu: "#ffffff", - highlights: darkDefaultTheme.colors!.highlights, - }, -} satisfies Theme; - -// The combined "red theme", -// we pass this to BlockNoteView and then the editor will automatically -// switch between lightRedTheme / darkRedTheme based on the system theme -const redTheme = { - light: lightRedTheme, - dark: darkRedTheme, -}; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: "Welcome to this demo!", - }, - { - type: "paragraph", - content: "Open up a menu or toolbar to see more of the red theme", - }, - { - type: "paragraph", - content: - "Toggle light/dark mode in the page footer and see the theme change too", - }, - { - type: "paragraph", - }, - ], - }); - - // Renders the editor instance using a React component. - return ; -} diff --git a/examples/03-theming/05-theming-css-variables-code/README.md b/examples/03-theming/05-theming-css-variables-code/README.md deleted file mode 100644 index a5c02c4ab1..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Changing Themes Through Code - -In this example, we use the `BlockNoteView` component's `theme` props to create a red theme for both light and dark modes. - -**Relevant Docs:** - -- [Changing CSS Variables Through Code](/docs/styling-theming/themes#changing-css-variables-through-code) \ No newline at end of file diff --git a/examples/03-theming/05-theming-css-variables-code/index.html b/examples/03-theming/05-theming-css-variables-code/index.html deleted file mode 100644 index aaba1aa427..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Changing Themes Through Code - - -
- - - diff --git a/examples/03-theming/05-theming-css-variables-code/main.tsx b/examples/03-theming/05-theming-css-variables-code/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/03-theming/05-theming-css-variables-code/package.json b/examples/03-theming/05-theming-css-variables-code/package.json deleted file mode 100644 index 29b7b64485..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-theming-css-variables-code", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/03-theming/05-theming-css-variables-code/tsconfig.json b/examples/03-theming/05-theming-css-variables-code/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/03-theming/05-theming-css-variables-code/vite.config.ts b/examples/03-theming/05-theming-css-variables-code/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/03-theming/05-theming-css-variables-code/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/02-ui-components/01-ui-elements-remove/.bnexample.json b/examples/03-ui-components/01-ui-elements-remove/.bnexample.json similarity index 100% rename from examples/02-ui-components/01-ui-elements-remove/.bnexample.json rename to examples/03-ui-components/01-ui-elements-remove/.bnexample.json diff --git a/examples/03-ui-components/01-ui-elements-remove/README.md b/examples/03-ui-components/01-ui-elements-remove/README.md new file mode 100644 index 0000000000..625fd0321b --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/README.md @@ -0,0 +1,7 @@ +# Removing UI Elements + +In this example, we remove all menus & toolbars, leaving only the editor. + +**Relevant Docs:** + +- [UI Components](/docs/react/components) diff --git a/examples/03-ui-components/01-ui-elements-remove/index.html b/examples/03-ui-components/01-ui-elements-remove/index.html new file mode 100644 index 0000000000..7b475d63b8 --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/index.html @@ -0,0 +1,14 @@ + + + + + Removing UI Elements + + + +
+ + + diff --git a/examples/03-ui-components/01-ui-elements-remove/main.tsx b/examples/03-ui-components/01-ui-elements-remove/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/01-ui-elements-remove/package.json b/examples/03-ui-components/01-ui-elements-remove/package.json new file mode 100644 index 0000000000..54a000524a --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-ui-elements-remove", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/01-ui-elements-remove/src/App.tsx b/examples/03-ui-components/01-ui-elements-remove/src/App.tsx new file mode 100644 index 0000000000..6f8b39a75f --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/src/App.tsx @@ -0,0 +1,40 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: + "There are no menus or toolbars in this editor, but you can still markup text using keyboard shortcuts.", + }, + { + type: "paragraph", + content: + "Try making text bold with Ctrl+B/Cmd+B or undo with Ctrl+Z/Cmd+Z.", + }, + ], + }); + + // Renders the editor instance. + return ( + + ); +} diff --git a/examples/03-ui-components/01-ui-elements-remove/tsconfig.json b/examples/03-ui-components/01-ui-elements-remove/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/01-ui-elements-remove/vite.config.ts b/examples/03-ui-components/01-ui-elements-remove/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/01-ui-elements-remove/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/.bnexample.json b/examples/03-ui-components/02-formatting-toolbar-buttons/.bnexample.json new file mode 100644 index 0000000000..e38b6fb26a --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/.bnexample.json @@ -0,0 +1,11 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Inline Content", + "UI Components", + "Formatting Toolbar" + ] +} diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/README.md b/examples/03-ui-components/02-formatting-toolbar-buttons/README.md new file mode 100644 index 0000000000..a315178355 --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/README.md @@ -0,0 +1,11 @@ +# Adding Formatting Toolbar Buttons + +In this example, we add a blue text/background color and code style button to the Formatting Toolbar. We also make sure it only shows up when some text is selected. + +**Try it out:** Select some text to open the Formatting Toolbar, and click one of the new buttons! + +**Relevant Docs:** + +- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar) +- [Manipulating Inline Content](/docs/reference/editor/manipulating-content) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/index.html b/examples/03-ui-components/02-formatting-toolbar-buttons/index.html new file mode 100644 index 0000000000..c347065176 --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/index.html @@ -0,0 +1,14 @@ + + + + + Adding Formatting Toolbar Buttons + + + +
+ + + diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/main.tsx b/examples/03-ui-components/02-formatting-toolbar-buttons/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json new file mode 100644 index 0000000000..2b1ed66d0d --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-formatting-toolbar-buttons", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/src/App.tsx b/examples/03-ui-components/02-formatting-toolbar-buttons/src/App.tsx new file mode 100644 index 0000000000..abd20cafce --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/src/App.tsx @@ -0,0 +1,118 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + BasicTextStyleButton, + BlockTypeSelect, + ColorStyleButton, + CreateLinkButton, + FileCaptionButton, + FileReplaceButton, + FormattingToolbar, + FormattingToolbarController, + NestBlockButton, + TextAlignButton, + UnnestBlockButton, + useCreateBlockNote, +} from "@blocknote/react"; + +import { BlueButton } from "./BlueButton"; + +const CustomFormattingToolbar = () => ( + + + + {/* Extra button to toggle blue text & background */} + + + + + + + + + + {/* Extra button to toggle code styles */} + + + + + + + + + + + + + +); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "You can now toggle ", + styles: {}, + }, + { + type: "text", + text: "blue", + styles: { textColor: "blue", backgroundColor: "blue" }, + }, + { + type: "text", + text: " and ", + styles: {}, + }, + { + type: "text", + text: "code", + styles: { code: true }, + }, + { + type: "text", + text: " styles with new buttons in the Formatting Toolbar", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: "Select some text to try them out", + }, + { + type: "image", + props: { + url: "https://placehold.co/332x322.jpg", + }, + }, + { + type: "paragraph", + content: + "Notice that the buttons don't appear when the image block above is selected, as it has no inline content.", + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/src/BlueButton.tsx b/examples/03-ui-components/02-formatting-toolbar-buttons/src/BlueButton.tsx new file mode 100644 index 0000000000..eedb7c6685 --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/src/BlueButton.tsx @@ -0,0 +1,45 @@ +import "@blocknote/mantine/style.css"; +import { + useBlockNoteEditor, + useComponentsContext, + useEditorState, + useSelectedBlocks, +} from "@blocknote/react"; + +// Custom Formatting Toolbar Button to toggle blue text & background color. +export function BlueButton() { + const editor = useBlockNoteEditor(); + + const Components = useComponentsContext()!; + + // Tracks whether the text & background are both blue. + const isSelected = useEditorState({ + editor, + selector: ({ editor }) => + editor.getActiveStyles().textColor === "blue" && + editor.getActiveStyles().backgroundColor === "blue", + }); + + // Doesn't render unless a at least one block with inline content is + // selected. You can use a similar pattern of returning `null` to + // conditionally render buttons based on the editor state. + const blocks = useSelectedBlocks(); + if (blocks.filter((block) => block.content !== undefined).length === 0) { + return null; + } + + return ( + { + editor.toggleStyles({ + textColor: "blue", + backgroundColor: "blue", + }); + }} + isSelected={isSelected} + > + Blue + + ); +} diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json b/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts b/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/02-formatting-toolbar-buttons/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/.bnexample.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/.bnexample.json new file mode 100644 index 0000000000..9d554dc51c --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/.bnexample.json @@ -0,0 +1,16 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Formatting Toolbar", + "Custom Schemas" + ], + "dependencies": { + "@mantine/core": "^9.0.2", + "react-icons": "^5.5.0" + } +} diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/README.md b/examples/03-ui-components/03-formatting-toolbar-block-type-items/README.md new file mode 100644 index 0000000000..6a4a20aae9 --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/README.md @@ -0,0 +1,11 @@ +# Adding Block Type Select Items + +In this example, we add an item to the Block Type Select, so that it works for a custom alert block we create. + +**Try it out:** Select some text to open the Formatting Toolbar, and click "Alert" in the Block Type Select to change the selected block! + +**Relevant Docs:** + +- [Changing Block Type Select Items](/docs/react/components/formatting-toolbar) +- [Custom Block Types](/docs/features/custom-schemas/custom-blocks) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/index.html b/examples/03-ui-components/03-formatting-toolbar-block-type-items/index.html new file mode 100644 index 0000000000..661579e8df --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/index.html @@ -0,0 +1,14 @@ + + + + + Adding Block Type Select Items + + + +
+ + + diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/main.tsx b/examples/03-ui-components/03-formatting-toolbar-block-type-items/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json new file mode 100644 index 0000000000..2cdf706ca5 --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-ui-components-formatting-toolbar-block-type-items", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx new file mode 100644 index 0000000000..4b2af03fbe --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/Alert.tsx @@ -0,0 +1,120 @@ +import { defaultProps } from "@blocknote/core"; +import { createReactBlockSpec } from "@blocknote/react"; +import { Menu } from "@mantine/core"; +import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; + +import "./styles.css"; + +// The types of alerts that users can choose from. +export const alertTypes = [ + { + title: "Warning", + value: "warning", + icon: MdError, + color: "#e69819", + backgroundColor: { + light: "#fff6e6", + dark: "#805d20", + }, + }, + { + title: "Error", + value: "error", + icon: MdCancel, + color: "#d80d0d", + backgroundColor: { + light: "#ffe6e6", + dark: "#802020", + }, + }, + { + title: "Info", + value: "info", + icon: MdInfo, + color: "#507aff", + backgroundColor: { + light: "#e6ebff", + dark: "#203380", + }, + }, + { + title: "Success", + value: "success", + icon: MdCheckCircle, + color: "#0bc10b", + backgroundColor: { + light: "#e6ffe6", + dark: "#208020", + }, + }, +] as const; + +// The Alert block. +export const Alert = createReactBlockSpec( + { + type: "alert", + propSchema: { + textAlignment: defaultProps.textAlignment, + textColor: defaultProps.textColor, + type: { + default: "warning", + values: ["warning", "error", "info", "success"], + }, + }, + content: "inline", + }, + { + render: (props) => { + const alertType = alertTypes.find( + (a) => a.value === props.block.props.type, + )!; + const Icon = alertType.icon; + return ( +
+ {/*Icon which opens a menu to choose the Alert type*/} + + +
+ +
+
+ {/*Dropdown to change the Alert type*/} + + Alert Type + + {alertTypes.map((type) => { + const ItemIcon = type.icon; + + return ( + + } + onClick={() => + props.editor.updateBlock(props.block, { + type: "alert", + props: { type: type.value }, + }) + } + > + {type.title} + + ); + })} + +
+ {/*Rich text field for user to type in*/} +
+
+ ); + }, + }, +); diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/App.tsx b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/App.tsx new file mode 100644 index 0000000000..eeb8e38887 --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/App.tsx @@ -0,0 +1,84 @@ +import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + FormattingToolbarController, + blockTypeSelectItems, + useBlockNoteEditor, + useCreateBlockNote, + BlockTypeSelectItem, + FormattingToolbar, +} from "@blocknote/react"; + +import { RiAlertFill } from "react-icons/ri"; +import { Alert } from "./Alert"; + +// Our schema with block specs, which contain the configs and implementations for +// blocks that we want our editor to use. +const schema = BlockNoteSchema.create({ + blockSpecs: { + // Adds all default blocks. + ...defaultBlockSpecs, + // Adds the Alert block. + alert: Alert(), + }, +}); + +const CustomFormattingToolbar = () => { + const editor = useBlockNoteEditor< + typeof schema.blockSchema, + typeof schema.inlineContentSchema, + typeof schema.styleSchema + >(); + + return ( + // Uses the default Formatting Toolbar. + + ); +}; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: + "Try selecting some text - you'll see the new 'Alert' item in the Block Type Select", + }, + { + type: "alert", + content: + "Or select text in this alert - the Block Type Select also appears", + }, + ], + }); + + // Renders the editor instance. + return ( + + {/* Replaces the default Formatting Toolbar */} + + + ); +} diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/styles.css b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/styles.css new file mode 100644 index 0000000000..a529138eee --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/src/styles.css @@ -0,0 +1,74 @@ +.alert { + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; + border-radius: 4px; + min-height: 48px; + padding: 4px; +} + +.alert[data-alert-type="warning"] { + background-color: #fff6e6; +} + +.alert[data-alert-type="error"] { + background-color: #ffe6e6; +} + +.alert[data-alert-type="info"] { + background-color: #e6ebff; +} + +.alert[data-alert-type="success"] { + background-color: #e6ffe6; +} + +[data-color-scheme="dark"] .alert[data-alert-type="warning"] { + background-color: #805d20; +} + +[data-color-scheme="dark"] .alert[data-alert-type="error"] { + background-color: #802020; +} + +[data-color-scheme="dark"] .alert[data-alert-type="info"] { + background-color: #203380; +} + +[data-color-scheme="dark"] .alert[data-alert-type="success"] { + background-color: #208020; +} + +.alert-icon-wrapper { + border-radius: 16px; + display: flex; + justify-content: center; + align-items: center; + margin-left: 12px; + margin-right: 12px; + height: 18px; + width: 18px; + user-select: none; + cursor: pointer; +} + +.alert-icon[data-alert-icon-type="warning"] { + color: #e69819; +} + +.alert-icon[data-alert-icon-type="error"] { + color: #d80d0d; +} + +.alert-icon[data-alert-icon-type="info"] { + color: #507aff; +} + +.alert-icon[data-alert-icon-type="success"] { + color: #0bc10b; +} + +.inline-content { + flex-grow: 1; +} diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts b/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/04-side-menu-buttons/.bnexample.json b/examples/03-ui-components/04-side-menu-buttons/.bnexample.json new file mode 100644 index 0000000000..2e64fc3cdb --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": ["Intermediate", "Blocks", "UI Components", "Block Side Menu"], + "dependencies": { + "react-icons": "^5.5.0" + } +} diff --git a/examples/03-ui-components/04-side-menu-buttons/README.md b/examples/03-ui-components/04-side-menu-buttons/README.md new file mode 100644 index 0000000000..da83c56399 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/README.md @@ -0,0 +1,11 @@ +# Adding Block Side Menu Buttons + +In this example, we replace the button to add a block in the Block Side Menu, with a button to remove the hovered block. + +**Try it out:** Hover a block to open the Block Side Menu, and click the new button! + +**Relevant Docs:** + +- [Changing the Block Side Menu](/docs/react/components/side-menu) +- [Removing Blocks](/docs/reference/editor/manipulating-content) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/04-side-menu-buttons/index.html b/examples/03-ui-components/04-side-menu-buttons/index.html new file mode 100644 index 0000000000..bc614baf60 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/index.html @@ -0,0 +1,14 @@ + + + + + Adding Block Side Menu Buttons + + + +
+ + + diff --git a/examples/03-ui-components/04-side-menu-buttons/main.tsx b/examples/03-ui-components/04-side-menu-buttons/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/04-side-menu-buttons/package.json b/examples/03-ui-components/04-side-menu-buttons/package.json new file mode 100644 index 0000000000..5515734743 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-ui-components-side-menu-buttons", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/04-side-menu-buttons/src/App.tsx b/examples/03-ui-components/04-side-menu-buttons/src/App.tsx new file mode 100644 index 0000000000..29a79fbbc9 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/src/App.tsx @@ -0,0 +1,47 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + DragHandleButton, + SideMenu, + SideMenuController, + SideMenuProps, + useCreateBlockNote, +} from "@blocknote/react"; + +import { RemoveBlockButton } from "./RemoveBlockButton"; + +const CustomSideMenu = (props: SideMenuProps) => ( + + {/* Button which removes the hovered block. */} + + + +); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "<- Notice the new button in the side menu", + }, + { + type: "paragraph", + content: "Click it to remove the hovered block", + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/04-side-menu-buttons/src/RemoveBlockButton.tsx b/examples/03-ui-components/04-side-menu-buttons/src/RemoveBlockButton.tsx new file mode 100644 index 0000000000..b5e2d058a4 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/src/RemoveBlockButton.tsx @@ -0,0 +1,37 @@ +import {} from "@blocknote/core"; +import { SideMenuExtension } from "@blocknote/core/extensions"; +import { + useBlockNoteEditor, + useComponentsContext, + useExtensionState, +} from "@blocknote/react"; +import { MdDelete } from "react-icons/md"; + +// Custom Side Menu button to remove the hovered block. +export function RemoveBlockButton() { + const editor = useBlockNoteEditor(); + + const Components = useComponentsContext()!; + + const block = useExtensionState(SideMenuExtension, { + selector: (state) => state?.block, + }); + + if (!block) { + return null; + } + + return ( + { + editor.removeBlocks([block]); + }} + /> + } + /> + ); +} diff --git a/examples/03-ui-components/04-side-menu-buttons/tsconfig.json b/examples/03-ui-components/04-side-menu-buttons/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/04-side-menu-buttons/vite.config.ts b/examples/03-ui-components/04-side-menu-buttons/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/04-side-menu-buttons/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/.bnexample.json b/examples/03-ui-components/05-side-menu-drag-handle-items/.bnexample.json new file mode 100644 index 0000000000..2e64fc3cdb --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": ["Intermediate", "Blocks", "UI Components", "Block Side Menu"], + "dependencies": { + "react-icons": "^5.5.0" + } +} diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/README.md b/examples/03-ui-components/05-side-menu-drag-handle-items/README.md new file mode 100644 index 0000000000..467c91ebd6 --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/README.md @@ -0,0 +1,11 @@ +# Adding Drag Handle Menu Items + +In this example, we add an item to the Drag Handle Menu, which resets the hovered block to a paragraph. + +**Try it out:** Hover a block to open the Block Side Menu, and click "Reset Type" in the Drag Handle Menu to reset the selected block! + +**Relevant Docs:** + +- [Changing Drag Handle Menu Items](/docs/react/components/side-menu) +- [Updating Blocks](/docs/reference/editor/manipulating-content) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/index.html b/examples/03-ui-components/05-side-menu-drag-handle-items/index.html new file mode 100644 index 0000000000..b6923ca7c1 --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/index.html @@ -0,0 +1,14 @@ + + + + + Adding Drag Handle Menu Items + + + +
+ + + diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/main.tsx b/examples/03-ui-components/05-side-menu-drag-handle-items/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json new file mode 100644 index 0000000000..b643fee1a9 --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-ui-components-side-menu-drag-handle-items", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/src/App.tsx b/examples/03-ui-components/05-side-menu-drag-handle-items/src/App.tsx new file mode 100644 index 0000000000..ec50019e0c --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/src/App.tsx @@ -0,0 +1,58 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + BlockColorsItem, + DragHandleMenu, + RemoveBlockItem, + SideMenu, + SideMenuController, + SideMenuProps, + useCreateBlockNote, +} from "@blocknote/react"; + +import { ResetBlockTypeItem } from "./ResetBlockTypeItem"; + +// To avoid rendering issues, it's good practice to define your custom drag +// handle menu in a separate component, instead of inline within the `sideMenu` +// prop of `SideMenuController`. +const CustomDragHandleMenu = () => ( + + Delete + Colors + {/* Item which resets the hovered block's type. */} + Reset Type + +); + +const CustomSideMenu = (props: SideMenuProps) => ( + +); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "<- Click the Drag Handle to see the new item", + }, + { + type: "bulletListItem", + content: + "Try resetting this block's type using the new Drag Handle Menu item", + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/src/ResetBlockTypeItem.tsx b/examples/03-ui-components/05-side-menu-drag-handle-items/src/ResetBlockTypeItem.tsx new file mode 100644 index 0000000000..20fad5d1a1 --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/src/ResetBlockTypeItem.tsx @@ -0,0 +1,32 @@ +import {} from "@blocknote/core"; +import { SideMenuExtension } from "@blocknote/core/extensions"; +import { + useBlockNoteEditor, + useComponentsContext, + useExtensionState, +} from "@blocknote/react"; +import { ReactNode } from "react"; + +export function ResetBlockTypeItem(props: { children: ReactNode }) { + const editor = useBlockNoteEditor(); + + const Components = useComponentsContext()!; + + const block = useExtensionState(SideMenuExtension, { + selector: (state) => state?.block, + }); + + if (!block) { + return null; + } + + return ( + { + editor.updateBlock(block, { type: "paragraph" }); + }} + > + {props.children} + + ); +} diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json b/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts b/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/05-side-menu-drag-handle-items/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/.bnexample.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/.bnexample.json new file mode 100644 index 0000000000..26081354ce --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/.bnexample.json @@ -0,0 +1,15 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Suggestion Menus", + "Slash Menu" + ], + "dependencies": { + "react-icons": "^5.5.0" + } +} diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/README.md b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/README.md new file mode 100644 index 0000000000..13336c828e --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/README.md @@ -0,0 +1,12 @@ +# Adding Slash Menu Items + +In this example, we add an item to the Slash Menu, which adds a new block below with a bold "Hello World" string. + +**Try it out:** Press the "/" key to open the Slash Menu and select the new item! + +**Relevant Docs:** + +- [Changing Slash Menu Items](/docs/react/components/suggestion-menus) +- [Getting Text Cursor Position](/docs/reference/editor/cursor-selections) +- [Inserting New Blocks](/docs/reference/editor/manipulating-content) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/index.html b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/index.html new file mode 100644 index 0000000000..2f89240213 --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/index.html @@ -0,0 +1,14 @@ + + + + + Adding Slash Menu Items + + + +
+ + + diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/main.tsx b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json new file mode 100644 index 0000000000..fd5d40c16f --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-slash-menu-items", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/src/App.tsx b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/src/App.tsx new file mode 100644 index 0000000000..906000401a --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/src/App.tsx @@ -0,0 +1,74 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { + filterSuggestionItems, + insertOrUpdateBlockForSlashMenu, +} from "@blocknote/core/extensions"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + DefaultReactSuggestionItem, + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; +import { HiOutlineGlobeAlt } from "react-icons/hi"; + +// Custom Slash Menu item to insert a block after the current one. +const insertHelloWorldItem = (editor: BlockNoteEditor) => ({ + title: "Insert Hello World", + onItemClick: () => + // If the block containing the text caret is empty, `insertOrUpdateBlock` + // changes its type to the provided block. Otherwise, it inserts the new + // block below and moves the text caret to it. We use this function with + // a block containing 'Hello World' in bold. + insertOrUpdateBlockForSlashMenu(editor, { + type: "paragraph", + content: [{ type: "text", text: "Hello World", styles: { bold: true } }], + }), + aliases: ["helloworld", "hw"], + group: "Other", + icon: , + subtext: "Used to insert a block with 'Hello World' below.", +}); + +// List containing all default Slash Menu Items, as well as our custom one. +const getCustomSlashMenuItems = ( + editor: BlockNoteEditor, +): DefaultReactSuggestionItem[] => [ + ...getDefaultReactSlashMenuItems(editor), + insertHelloWorldItem(editor), +]; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu", + }, + { + type: "paragraph", + content: "Notice the new 'Insert Hello World' item - try it out!", + }, + ], + }); + + // Renders the editor instance. + return ( + + + filterSuggestionItems(getCustomSlashMenuItems(editor), query) + } + /> + + ); +} diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/02-ui-components/07-suggestion-menus-slash-menu-component/.bnexample.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/.bnexample.json similarity index 100% rename from examples/02-ui-components/07-suggestion-menus-slash-menu-component/.bnexample.json rename to examples/03-ui-components/07-suggestion-menus-slash-menu-component/.bnexample.json diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/README.md b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/README.md new file mode 100644 index 0000000000..31831ebf83 --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/README.md @@ -0,0 +1,10 @@ +# Replacing Slash Menu Component + +In this example, we replace the default Slash Menu component with a basic custom one. + +**Try it out:** Press the "/" key to see the new Slash Menu! + +**Relevant Docs:** + +- [Replacing the Slash Menu Component](/docs/react/components/suggestion-menus) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/index.html b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/index.html new file mode 100644 index 0000000000..60c4faad55 --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/index.html @@ -0,0 +1,14 @@ + + + + + Replacing Slash Menu Component + + + +
+ + + diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/main.tsx b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json new file mode 100644 index 0000000000..7dcbfd0445 --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-slash-menu-component", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/src/App.tsx b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/src/App.tsx new file mode 100644 index 0000000000..c7af9b48a1 --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/src/App.tsx @@ -0,0 +1,63 @@ +import "@blocknote/core/fonts/inter.css"; +import { + DefaultReactSuggestionItem, + SuggestionMenuController, + SuggestionMenuProps, + useCreateBlockNote, +} from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +import "./styles.css"; + +// Custom component to replace the default Slash Menu. +function CustomSlashMenu( + props: SuggestionMenuProps, +) { + return ( +
+ {props.items.map((item, index) => ( +
{ + props.onItemClick?.(item); + }} + > + {item.title} +
+ ))} +
+ ); +} + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu", + }, + { + type: "paragraph", + content: "It's been replaced with a custom component", + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/src/styles.css b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/src/styles.css new file mode 100644 index 0000000000..8d0eda1fef --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/src/styles.css @@ -0,0 +1,40 @@ +.slash-menu { + background-color: white; + border: 1px solid lightgray; + border-radius: 2px; + box-shadow: 0 0 8px #dddddd; + + display: flex; + flex-direction: column; + gap: 8px; + height: fit-content; + max-height: inherit; + + overflow: auto; + + padding: 8px; + + top: 8px; +} + +.slash-menu-item { + background-color: white; + border: 1px solid lightgray; + border-radius: 2px; + box-shadow: 0 0 4px #dddddd; + + cursor: pointer; + + font-size: 16px; + + align-items: center; + display: flex; + flex-direction: row; + + padding: 8px; +} + +.slash-menu-item:hover, +.slash-menu-item.selected { + background-color: lightgray; +} diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/.bnexample.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/.bnexample.json new file mode 100644 index 0000000000..3dfb6cf780 --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/.bnexample.json @@ -0,0 +1,12 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Suggestion Menus", + "Emoji Picker" + ] +} diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/README.md b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/README.md new file mode 100644 index 0000000000..124ab50535 --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/README.md @@ -0,0 +1,10 @@ +# Changing Emoji Picker Columns + +In this example, we change the Emoji Picker to display 5 columns instead of 10. + +**Try it out:** Press the ":" key to open the Emoji Picker! + +**Relevant Docs:** + +- [Changing Emoji Picker Columns](/docs/react/components/suggestion-menus) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/index.html b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/index.html new file mode 100644 index 0000000000..622c168d0a --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/index.html @@ -0,0 +1,14 @@ + + + + + Changing Emoji Picker Columns + + + +
+ + + diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/main.tsx b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json new file mode 100644 index 0000000000..6b755fa86f --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-emoji-picker-columns", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/src/App.tsx b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/src/App.tsx new file mode 100644 index 0000000000..fe4f1e4ed4 --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/src/App.tsx @@ -0,0 +1,39 @@ +import "@blocknote/core/fonts/inter.css"; +import { + GridSuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Press the ':' key to open the Emoji Picker", + }, + { + type: "paragraph", + content: "There are now 5 columns instead of 10", + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/.bnexample.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/.bnexample.json new file mode 100644 index 0000000000..7bfc6d29e1 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/.bnexample.json @@ -0,0 +1,12 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Intermediate", + "UI Components", + "Suggestion Menus", + "Emoji Picker", + "Appearance & Styling" + ] +} diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/README.md b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/README.md new file mode 100644 index 0000000000..85590bd013 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/README.md @@ -0,0 +1,10 @@ +# Replacing Emoji Picker Component + +In this example, we replace the default Emoji Picker component with a basic custom one. + +**Try it out:** Press the ":" key to see the new Emoji Picker! + +**Relevant Docs:** + +- [Replacing the Emoji Picker Component](/docs/react/components/suggestion-menus) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/index.html b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/index.html new file mode 100644 index 0000000000..da741ebd66 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/index.html @@ -0,0 +1,14 @@ + + + + + Replacing Emoji Picker Component + + + +
+ + + diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/main.tsx b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json new file mode 100644 index 0000000000..d03601a77b --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-emoji-picker-component", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/src/App.tsx b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/src/App.tsx new file mode 100644 index 0000000000..88379a6dd0 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/src/App.tsx @@ -0,0 +1,70 @@ +import "@blocknote/core/fonts/inter.css"; +import { + DefaultReactGridSuggestionItem, + GridSuggestionMenuController, + GridSuggestionMenuProps, + useCreateBlockNote, +} from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +import "./styles.css"; + +// Custom component to replace the default Emoji Picker. +function CustomEmojiPicker( + props: GridSuggestionMenuProps, +) { + return ( +
+ {props.items.map((item, index) => ( +
{ + props.onItemClick?.(item); + }} + > + {item.icon} +
+ ))} +
+ ); +} + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Press the ':' key to open the Emoji Picker", + }, + { + type: "paragraph", + content: "It's been replaced with a custom component", + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/src/styles.css b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/src/styles.css new file mode 100644 index 0000000000..1688511e20 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/src/styles.css @@ -0,0 +1,40 @@ +.emoji-picker { + background-color: white; + border: 1px solid lightgray; + border-radius: 2px; + box-shadow: 0 0 8px #dddddd; + + display: grid; + flex-direction: column; + gap: 8px; + height: fit-content; + max-height: inherit; + + overflow: auto; + + padding: 8px; + + top: 8px; +} + +.emoji-picker-item { + background-color: white; + border: 1px solid lightgray; + border-radius: 2px; + box-shadow: 0 0 4px #dddddd; + + cursor: pointer; + + font-size: 16px; + + align-items: center; + display: flex; + flex-direction: row; + + padding: 8px; +} + +.emoji-picker-item:hover, +.emoji-picker-item.selected { + background-color: lightgray; +} diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/05-custom-schema/02-suggestion-menus-mentions/.bnexample.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/.bnexample.json similarity index 100% rename from examples/05-custom-schema/02-suggestion-menus-mentions/.bnexample.json rename to examples/03-ui-components/10-suggestion-menus-grid-mentions/.bnexample.json diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/README.md b/examples/03-ui-components/10-suggestion-menus-grid-mentions/README.md new file mode 100644 index 0000000000..f813a631af --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/README.md @@ -0,0 +1,11 @@ +# Grid Mentions Menu + +In this example, we create a custom `Mention` inline content type which is used to tag people. In addition, we create a new Suggestion Menu for mentions which opens with the "@" character. This Suggestion Menu is displayed as a grid of 2 columns, where each item is the first letter of the person's name. + +**Try it out:** Press the "@" key to open the mentions menu and insert a mention! + +**Relevant Docs:** + +- [Custom Inline Content Types](/docs/features/custom-schemas/custom-inline-content) +- [Creating Suggestion Menus](/docs/react/components/suggestion-menus) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/index.html b/examples/03-ui-components/10-suggestion-menus-grid-mentions/index.html new file mode 100644 index 0000000000..79b6b18bd7 --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/index.html @@ -0,0 +1,14 @@ + + + + + Grid Mentions Menu + + + +
+ + + diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/main.tsx b/examples/03-ui-components/10-suggestion-menus-grid-mentions/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json new file mode 100644 index 0000000000..26ed7774c4 --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-grid-mentions", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/App.tsx b/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/App.tsx new file mode 100644 index 0000000000..8050cadb21 --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/App.tsx @@ -0,0 +1,102 @@ +import { BlockNoteSchema, defaultInlineContentSpecs } from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + DefaultReactGridSuggestionItem, + GridSuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; + +import { Mention } from "./Mention"; + +// Our schema with inline content specs, which contain the configs and +// implementations for inline content that we want our editor to use. +const schema = BlockNoteSchema.create({ + inlineContentSpecs: { + // Adds all default inline content. + ...defaultInlineContentSpecs, + // Adds the mention tag. + mention: Mention, + }, +}); + +// Function which gets all users for the mentions menu. +const getMentionMenuItems = ( + editor: typeof schema.BlockNoteEditor, +): DefaultReactGridSuggestionItem[] => { + const users = ["Steve", "Bob", "Joe", "Mike"]; + + return users.map((user) => ({ + id: user, + onItemClick: () => { + editor.insertInlineContent([ + { + type: "mention", + props: { + user, + }, + }, + " ", // add a space after the mention + ]); + }, + icon:

{user.substring(0, 1)}

, + })); +}; + +export function App() { + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: [ + { + type: "mention", + props: { + user: "Steve", + }, + }, + { + type: "text", + text: " <- This is an example mention", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: "Press the '@' key to open the mentions menu and add another", + }, + ], + }); + + return ( + + {/* Adds a mentions menu which opens with the "@" key */} + + // Gets the mentions menu items + // TODO: Fix map/type cast + filterSuggestionItems( + getMentionMenuItems(editor).map((item) => ({ + ...item, + title: item.id, + })), + query, + ) as DefaultReactGridSuggestionItem[] + } + columns={2} + minQueryLength={2} + /> + + ); +} + +export default App; diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx b/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx new file mode 100644 index 0000000000..b19366ff65 --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/src/Mention.tsx @@ -0,0 +1,21 @@ +import { createReactInlineContentSpec } from "@blocknote/react"; + +// The Mention inline content. +export const Mention = createReactInlineContentSpec( + { + type: "mention", + propSchema: { + user: { + default: "Unknown", + }, + }, + content: "none", + }, + { + render: (props) => ( + + @{props.inlineContent.props.user} + + ), + }, +); diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts b/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/11-uppy-file-panel/.bnexample.json b/examples/03-ui-components/11-uppy-file-panel/.bnexample.json new file mode 100644 index 0000000000..49477b124d --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/.bnexample.json @@ -0,0 +1,21 @@ +{ + "playground": true, + "docs": true, + "author": "ezhil56x", + "tags": ["Intermediate", "Files"], + "dependencies": { + "@uppy/core": "^3.13.1", + "@uppy/dashboard": "^3.9.1", + "@uppy/drag-drop": "^3.1.1", + "@uppy/file-input": "^3.1.2", + "@uppy/image-editor": "^2.4.6", + "@uppy/progress-bar": "^3.1.1", + "@uppy/react": "^3.4.0", + "@uppy/screen-capture": "^3.2.0", + "@uppy/status-bar": "^3.1.1", + "@uppy/webcam": "^3.4.2", + "@uppy/xhr-upload": "^3.4.0", + "react-icons": "^5.5.0" + }, + "pro": true +} diff --git a/examples/03-ui-components/11-uppy-file-panel/README.md b/examples/03-ui-components/11-uppy-file-panel/README.md new file mode 100644 index 0000000000..3717bc063d --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/README.md @@ -0,0 +1,19 @@ +# Uppy File Panel + +This example allows users to upload files using [Uppy](https://uppy.io/) by replacing the default File Panel with an Uppy Dashboard. + +Uppy is highly extensible and has an extensive ecosystem of plugins. For example, you can: + +- Record audio, screen or webcam +- Import files from Box / Dropbox / Facebook / Google Drive / Google Photos / Instagram / OneDrive / Zoom +- Select files from Unsplash +- Show an image editor (crop, rotate, etc) + +In this example, we've enabled the Webcam, ScreenCapture and Image Editor plugins. + +**Try it out:** Click the "Add Image" button and you can either drop files or click "browse files" to upload them. + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) +- [Image](/docs/foundations/schemas) diff --git a/examples/03-ui-components/11-uppy-file-panel/index.html b/examples/03-ui-components/11-uppy-file-panel/index.html new file mode 100644 index 0000000000..5e95a451fe --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/index.html @@ -0,0 +1,14 @@ + + + + + Uppy File Panel + + + +
+ + + diff --git a/examples/03-ui-components/11-uppy-file-panel/main.tsx b/examples/03-ui-components/11-uppy-file-panel/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json new file mode 100644 index 0000000000..d8e52bf349 --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/package.json @@ -0,0 +1,42 @@ +{ + "name": "@blocknote/example-ui-components-uppy-file-panel", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@uppy/core": "^3.13.1", + "@uppy/dashboard": "^3.9.1", + "@uppy/drag-drop": "^3.1.1", + "@uppy/file-input": "^3.1.2", + "@uppy/image-editor": "^2.4.6", + "@uppy/progress-bar": "^3.1.1", + "@uppy/react": "^3.4.0", + "@uppy/screen-capture": "^3.2.0", + "@uppy/status-bar": "^3.1.1", + "@uppy/webcam": "^3.4.2", + "@uppy/xhr-upload": "^3.4.0", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/11-uppy-file-panel/src/App.tsx b/examples/03-ui-components/11-uppy-file-panel/src/App.tsx new file mode 100644 index 0000000000..994710692b --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/src/App.tsx @@ -0,0 +1,57 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + FilePanelController, + FormattingToolbar, + FormattingToolbarController, + FormattingToolbarProps, + getFormattingToolbarItems, + useCreateBlockNote, +} from "@blocknote/react"; + +import { FileReplaceButton } from "./FileReplaceButton"; +import { uploadFile, UppyFilePanel } from "./UppyFilePanel"; + +const CustomFormattingToolbar = (props: FormattingToolbarProps) => { + // Replaces default file replace button with one that opens Uppy. + const items = getFormattingToolbarItems(); + items.splice( + items.findIndex((c) => c.key === "replaceFileButton"), + 1, + , + ); + + return {items}; +}; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Upload an image using the button below", + }, + { + type: "image", + }, + ], + uploadFile, + }); + + // Renders the editor instance using a React component. + return ( + + + {/* Replaces default file panel with Uppy one. */} + + + ); +} diff --git a/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx b/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx new file mode 100644 index 0000000000..d3f393b04c --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/src/FileReplaceButton.tsx @@ -0,0 +1,77 @@ +import { + BlockSchema, + blockHasType, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { + useBlockNoteEditor, + useComponentsContext, + useDictionary, + useSelectedBlocks, +} from "@blocknote/react"; +import { useEffect, useState } from "react"; + +import { RiImageEditFill } from "react-icons/ri"; + +import { UppyFilePanel } from "./UppyFilePanel"; + +// Copied with minor changes from: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultButtons/FileReplaceButton.tsx +// Opens Uppy file panel instead of the default one. +export const FileReplaceButton = () => { + const dict = useDictionary(); + const Components = useComponentsContext()!; + + const editor = useBlockNoteEditor< + BlockSchema, + InlineContentSchema, + StyleSchema + >(); + + const selectedBlocks = useSelectedBlocks(editor); + + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + setIsOpen(false); + }, [selectedBlocks]); + + const block = selectedBlocks.length === 1 ? selectedBlocks[0] : undefined; + + if ( + block === undefined || + !blockHasType(block, editor, "file", { url: "string" }) || + !editor.isEditable + ) { + return null; + } + + return ( + + + setIsOpen(!isOpen)} + isSelected={isOpen} + mainTooltip={ + dict.formatting_toolbar.file_replace.tooltip[block.type] || + dict.formatting_toolbar.file_replace.tooltip["file"] + } + label={ + dict.formatting_toolbar.file_replace.tooltip[block.type] || + dict.formatting_toolbar.file_replace.tooltip["file"] + } + icon={} + /> + + + {/* Replaces default file panel with our Uppy one. */} + + + + ); +}; diff --git a/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx b/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx new file mode 100644 index 0000000000..4094bc4441 --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/src/UppyFilePanel.tsx @@ -0,0 +1,104 @@ +import { FilePanelProps, useBlockNoteEditor } from "@blocknote/react"; +import Uppy, { UploadSuccessCallback } from "@uppy/core"; +import "@uppy/core/dist/style.min.css"; +import "@uppy/dashboard/dist/style.min.css"; +import { Dashboard } from "@uppy/react"; +import XHR from "@uppy/xhr-upload"; +import { useEffect } from "react"; + +// Image editor plugin +import ImageEditor from "@uppy/image-editor"; +import "@uppy/image-editor/dist/style.min.css"; + +// Screen capture plugin +import ScreenCapture from "@uppy/screen-capture"; +import "@uppy/screen-capture/dist/style.min.css"; + +// Webcam plugin +import Webcam from "@uppy/webcam"; +import "@uppy/webcam/dist/style.min.css"; + +// Configure your Uppy instance here. +const uppy = new Uppy() + // Enabled plugins - you probably want to customize this + // See https://uppy.io/examples/ for all the integrations like Google Drive, + // Instagram Dropbox etc. + .use(Webcam) + .use(ScreenCapture) + .use(ImageEditor) + + // Uses an XHR upload plugin to upload files to tmpfiles.org. + // You want to replace this with your own upload endpoint or Uppy Companion + // server. + .use(XHR, { + endpoint: "https://tmpfiles.org/api/v1/upload", + getResponseData(text, resp) { + return { + url: JSON.parse(text).data.url.replace( + "tmpfiles.org/", + "tmpfiles.org/dl/", + ), + }; + }, + }); + +export function UppyFilePanel(props: FilePanelProps) { + const { blockId } = props; + const editor = useBlockNoteEditor(); + + useEffect(() => { + // Listen for successful tippy uploads, and then update the Block with the + // uploaded URL. + const handler: UploadSuccessCallback> = ( + file, + response, + ) => { + if (!file) { + return; + } + + if (file.source === "uploadFile") { + // Didn't originate from Dashboard, should be handled by `uploadFile` + return; + } + if (response.status === 200) { + const updateData = { + props: { + name: file?.name, + url: response.uploadURL, + }, + }; + editor.updateBlock(blockId, updateData); + + // File should be removed from the Uppy instance after upload. + uppy.removeFile(file.id); + } + }; + uppy.on("upload-success", handler); + return () => { + uppy.off("upload-success", handler); + }; + }, [blockId, editor]); + + // set up dashboard as in https://uppy.io/examples/ + return ; +} + +// Implementation for the BlockNote `uploadFile` function. +// This function is used when for example, files are dropped into the editor. +export async function uploadFile(file: File) { + const id = uppy.addFile({ + id: file.name, + name: file.name, + type: file.type, + data: file, + source: "uploadFile", + }); + + try { + const result = await uppy.upload(); + return result.successful[0].response!.uploadURL!; + } finally { + uppy.removeFile(id); + } +} diff --git a/examples/03-ui-components/11-uppy-file-panel/tsconfig.json b/examples/03-ui-components/11-uppy-file-panel/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/11-uppy-file-panel/vite.config.ts b/examples/03-ui-components/11-uppy-file-panel/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/11-uppy-file-panel/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/12-static-formatting-toolbar/.bnexample.json b/examples/03-ui-components/12-static-formatting-toolbar/.bnexample.json new file mode 100644 index 0000000000..3f38ca0525 --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/.bnexample.json @@ -0,0 +1,11 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "UI Components", + "Formatting Toolbar", + "Appearance & Styling" + ] +} diff --git a/examples/03-ui-components/12-static-formatting-toolbar/README.md b/examples/03-ui-components/12-static-formatting-toolbar/README.md new file mode 100644 index 0000000000..9f008cc867 --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/README.md @@ -0,0 +1,9 @@ +# Static Formatting Toolbar + +This example shows how to make the formatting toolbar always visible and static +above the editor. + +**Relevant Docs:** + +- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/12-static-formatting-toolbar/index.html b/examples/03-ui-components/12-static-formatting-toolbar/index.html new file mode 100644 index 0000000000..7b8e963628 --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/index.html @@ -0,0 +1,14 @@ + + + + + Static Formatting Toolbar + + + +
+ + + diff --git a/examples/03-ui-components/12-static-formatting-toolbar/main.tsx b/examples/03-ui-components/12-static-formatting-toolbar/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/12-static-formatting-toolbar/package.json b/examples/03-ui-components/12-static-formatting-toolbar/package.json new file mode 100644 index 0000000000..4875a6ff6e --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-static-formatting-toolbar", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/12-static-formatting-toolbar/src/App.tsx b/examples/03-ui-components/12-static-formatting-toolbar/src/App.tsx new file mode 100644 index 0000000000..7889f0b342 --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/src/App.tsx @@ -0,0 +1,35 @@ +import "@blocknote/core/fonts/inter.css"; +import { FormattingToolbar, useCreateBlockNote } from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +import "./style.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Check out the static formatting toolbar above!", + }, + ], + }); + + // Renders the editor instance using a React component. + return ( + // Disables the default formatting toolbar and re-adds it without the + // `FormattingToolbarController` component. You may have seen + // `FormattingToolbarController` used in other examples, but we omit it here + // as we want to control the position and visibility ourselves. BlockNote + // also uses the `FormattingToolbarController` when displaying the + // Formatting Toolbar by default. + + + + ); +} diff --git a/examples/03-ui-components/12-static-formatting-toolbar/src/style.css b/examples/03-ui-components/12-static-formatting-toolbar/src/style.css new file mode 100644 index 0000000000..98e93611cd --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/src/style.css @@ -0,0 +1,9 @@ +.bn-container { + display: flex; + flex-direction: column-reverse; + gap: 8px; +} + +.bn-formatting-toolbar { + margin-inline: auto; +} diff --git a/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json b/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts b/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/12-static-formatting-toolbar/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/13-custom-ui/.bnexample.json b/examples/03-ui-components/13-custom-ui/.bnexample.json new file mode 100644 index 0000000000..02811781ee --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/.bnexample.json @@ -0,0 +1,20 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Advanced", + "Inline Content", + "UI Components", + "Block Side Menu", + "Formatting Toolbar", + "Suggestion Menus", + "Slash Menu", + "Appearance & Styling" + ], + "dependencies": { + "@mui/icons-material": "^5.16.1", + "@mui/material": "^5.16.1" + }, + "pro": true +} diff --git a/examples/03-ui-components/13-custom-ui/README.md b/examples/03-ui-components/13-custom-ui/README.md new file mode 100644 index 0000000000..e13bd69f6e --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/README.md @@ -0,0 +1,11 @@ +# UI With Third-Party Components + +In this example, we implement a basic editor interface using components from Material UI. We replace the Formatting Toolbar, Slash Menu, and Block Side Menu while disabling the other default elements. Additionally, the Formatting Toolbar is made static and always visible above the editor. + +**Relevant Docs:** + +- [Formatting Toolbar](/docs/react/components/formatting-toolbar) +- [Manipulating Inline Content](/docs/reference/editor/manipulating-content) +- [Slash Menu](/docs/react/components/suggestion-menus) +- [Side Menu](/docs/react/components/side-menu) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/13-custom-ui/index.html b/examples/03-ui-components/13-custom-ui/index.html new file mode 100644 index 0000000000..dac541ba99 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/index.html @@ -0,0 +1,14 @@ + + + + + UI With Third-Party Components + + + +
+ + + diff --git a/examples/03-ui-components/13-custom-ui/main.tsx b/examples/03-ui-components/13-custom-ui/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json new file mode 100644 index 0000000000..ff73939c76 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/package.json @@ -0,0 +1,32 @@ +{ + "name": "@blocknote/example-ui-components-custom-ui", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "@mui/icons-material": "^5.16.1", + "@mui/material": "^5.16.1" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/13-custom-ui/src/App.tsx b/examples/03-ui-components/13-custom-ui/src/App.tsx new file mode 100644 index 0000000000..40974fedbc --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/src/App.tsx @@ -0,0 +1,88 @@ +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import "@blocknote/core/fonts/inter.css"; +import { + BlockNoteViewRaw, + getDefaultReactSlashMenuItems, + SideMenuController, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; +import "@blocknote/react/style.css"; +import { createTheme, ThemeProvider, useMediaQuery } from "@mui/material"; +import { useMemo } from "react"; + +import { schema } from "./schema"; +import { CustomMUIFormattingToolbar } from "./MUIFormattingToolbar"; +import { CustomMUISideMenu } from "./MUISideMenu"; +import { MUISuggestionMenu } from "./MUISuggestionMenu"; + +import "./style.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + ], + }); + + // Automatically sets light/dark mode based on the user's system preferences. + const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); + const theme = useMemo( + () => + createTheme({ + palette: { + mode: prefersDarkMode ? "dark" : "light", + }, + }), + [prefersDarkMode], + ); + + // Renders the editor instance. + return ( + // Provides theming for Material UI. + + + {/* Adds the custom Formatting Toolbar. */} + {/* `FormattingToolbarController isn't used since we make the custom */} + {/* toolbar static and always visible above the editor for this */} + {/* example. */} + + {/* Adds the custom Side Menu and Slash Menu. */} + {/* These use controllers since we want them to be positioned and */} + {/* show/hide the same as the default ones. */} + + + filterSuggestionItems( + getDefaultReactSlashMenuItems(editor).filter( + (item) => item.title !== "Emoji", + ), + query, + ) + } + suggestionMenuComponent={MUISuggestionMenu} + onItemClick={(i) => i.onItemClick()} + /> + + + ); +} diff --git a/examples/03-ui-components/13-custom-ui/src/MUIFormattingToolbar.tsx b/examples/03-ui-components/13-custom-ui/src/MUIFormattingToolbar.tsx new file mode 100644 index 0000000000..0c4d217a1d --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/src/MUIFormattingToolbar.tsx @@ -0,0 +1,488 @@ +import { Block } from "@blocknote/core"; +import { + blockTypeSelectItems, + useBlockNoteEditor, + useEditorState, +} from "@blocknote/react"; +import { + Done, + FormatAlignCenter, + FormatAlignLeft, + FormatAlignRight, + FormatBold, + FormatColorText, + FormatItalic, + FormatStrikethrough, + FormatUnderlined, +} from "@mui/icons-material"; +import { + AppBar, + Box, + Button, + ButtonGroup, + Container, + Divider, + FormControl, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Select, + SelectChangeEvent, + Toolbar, + Tooltip, + Typography, +} from "@mui/material"; +import { + MouseEvent, + useCallback, + useState, + useMemo, + FC, + ReactNode, +} from "react"; + +import { TextBlockSchema } from "./schema"; + +// This replaces the generic Mantine `ToolbarSelect` component with a simplified +// MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/toolbar/ToolbarSelect.tsx +// In this example, we use it to create a replacement for the default Formatting +// Toolbar select element (i.e. the Block Type Select) using MUI, but you can +// also use it to add custom select elements. +function MUIToolbarSelect(props: { + items: Item[]; + selectedItem: Item; + onChange: (event: SelectChangeEvent) => void; +}) { + return ( + + `${ + theme.palette.mode === "dark" + ? theme.palette.primary.main + : theme.palette.background.default + } !important`, + borderColor: (theme) => + `${ + theme.palette.mode === "dark" + ? theme.palette.primary.main + : theme.palette.background.default + } !important`, + }, + }} + > + + + ); +} + +// This replaces the default `BlockTypeSelect` component with a simplified MUI +// version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultSelects/BlockTypeSelect.tsx +function MUIBlockTypeSelect() { + const editor = useBlockNoteEditor(); + + // The block currently containing the text cursor. + const block = useEditorState({ + editor, + selector: ({ editor }) => editor.getTextCursorPosition().block, + }); + + // Gets the default items for the select. + const defaultBlockTypeSelectItems = useMemo( + () => blockTypeSelectItems(editor.dictionary), + [editor.dictionary], + ); + + // Gets the selected item. + const selectedItem = useMemo( + () => + defaultBlockTypeSelectItems.find((item) => { + const typesMatch = item.type === block.type; + const propsMatch = + Object.entries(item.props || {}).filter( + ([propName, propValue]) => + propValue !== (block as any).props[propName], + ).length === 0; + + return typesMatch && propsMatch; + })!, + [defaultBlockTypeSelectItems, block], + ); + + // Updates the state when the user chooses an item. + const onChange = useCallback( + (event: SelectChangeEvent) => { + const newSelectedItem = defaultBlockTypeSelectItems.find( + (item) => item.name === event.target.value, + )!; + + editor.updateBlock(block, { + type: newSelectedItem.type as keyof TextBlockSchema, + props: newSelectedItem.props, + }); + editor.focus(); + }, + [block, defaultBlockTypeSelectItems, editor], + ); + + return ( + + ); +} + +// This replaces the generic Mantine `ToolbarButton` component with a simplified +// MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/toolbar/ToolbarButton.tsx +// In this example, we use it to create replacements for the default Formatting +// Toolbar buttons using MUI, but you can also use it to add custom buttons. +function MUIToolbarButton(props: { + tooltip: string; + selected?: boolean; + onClick: (event: MouseEvent) => void; + children: ReactNode; +}) { + return ( + + + + ); +} + +const basicTextStyleIcons = { + bold: FormatBold, + italic: FormatItalic, + underline: FormatUnderlined, + strike: FormatStrikethrough, +}; + +// This replaces the default `BasicTextStyleButton` component with a simplified +// MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx +function MUIBasicTextStyleButton(props: { + textStyle: "bold" | "italic" | "underline" | "strike"; +}) { + const Icon = basicTextStyleIcons[props.textStyle]; + const editor = useBlockNoteEditor(); + + // Whether the text style is currently active. + const textStyleActive = useEditorState({ + editor, + selector: ({ editor }) => props.textStyle in editor.getActiveStyles(), + }); + + // Tooltip for the button. + const tooltip = useMemo( + () => + `Toggle ${props.textStyle + .slice(0, 1) + .toUpperCase()}${props.textStyle.slice(1)}`, + [props.textStyle], + ); + + // Toggles the text style when the button is clicked. + const onClick = useCallback(() => { + editor.toggleStyles({ [props.textStyle]: true }); + editor.focus(); + }, [editor, props.textStyle]); + + return ( + + + + ); +} + +const textAlignIcons = { + left: FormatAlignLeft, + center: FormatAlignCenter, + right: FormatAlignRight, +}; + +// This replaces the default `TextAlignButton` component with a simplified MUI +// version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultButtons/TextAlignButton.tsx +function MUITextAlignButton(props: { + textAlignment: "left" | "center" | "right"; +}) { + const Icon = textAlignIcons[props.textAlignment]; + const editor = useBlockNoteEditor(); + + // The text alignment of the block currently containing the text cursor. + const activeTextAlignment = useEditorState({ + editor, + selector: ({ editor }) => { + const props = editor.getTextCursorPosition().block.props; + if ("textAlignment" in props) { + return props.textAlignment; + } + return undefined; + }, + }); + + // Tooltip for the button. + const tooltip = useMemo( + () => + `Align ${props.textAlignment + .slice(0, 1) + .toUpperCase()}${props.textAlignment.slice(1)}`, + [props.textAlignment], + ); + + // Sets the text alignment of the block currently containing the text cursor + // when the button is clicked. + const onClick = useCallback(() => { + editor.updateBlock(editor.getTextCursorPosition().block, { + props: { textAlignment: props.textAlignment }, + }); + editor.focus(); + }, [editor, props.textAlignment]); + + if (!activeTextAlignment) { + return null; + } + + return ( + + + + ); +} + +// The highlight colors used by BlockNote. +const colors = [ + "default", + "red", + "orange", + "yellow", + "green", + "blue", + "purple", +] as const; + +// This replaces the default `ColorStyleButton` component with a simplified MUI +// version. The original component can be found here: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx +function MUIColorStyleButton() { + const editor = useBlockNoteEditor(); + + // Anchor/trigger element for the color menu. + const [anchorEl, setAnchorEl] = useState(null); + + // The active text and background colors. + const activeTextColor = useEditorState({ + editor, + selector: ({ editor }) => editor.getActiveStyles().textColor || "default", + }); + const activeBackgroundColor = useEditorState({ + editor, + selector: ({ editor }) => + editor.getActiveStyles().backgroundColor || "default", + }); + + // Handles opening and closing the color menu. + const onClick = useCallback( + (event: MouseEvent) => setAnchorEl(event.currentTarget), + [], + ); + const onClose = useCallback(() => setAnchorEl(null), []); + + // Set the text or background color and close the color menu when a color is + // clicked. + const textColorOnClick = useCallback( + (textColor: string) => { + setAnchorEl(null); + textColor === "default" + ? editor.removeStyles({ textColor }) + : editor.addStyles({ textColor }); + setTimeout(() => editor.focus()); + }, + [editor], + ); + const backgroundColorOnClick = useCallback( + (backgroundColor: string) => { + setAnchorEl(null); + backgroundColor === "default" + ? editor.removeStyles({ backgroundColor }) + : editor.addStyles({ backgroundColor }); + setTimeout(() => editor.focus()); + }, + [editor], + ); + + return ( + <> + + + + + + Text Color + + {colors.map((color) => ( + textColorOnClick(color)}> + + + + + + {color.slice(0, 1).toUpperCase() + color.slice(1)} + + + {color === activeTextColor && ( + + )} + + ))} + + + Background Color + + {colors.map((color) => ( + backgroundColorOnClick(color)}> + + + + + + {color.slice(0, 1).toUpperCase() + color.slice(1)} + + + {color === activeBackgroundColor && ( + + )} + + ))} + + + ); +} + +// This replaces the generic Mantine `Toolbar` component: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/toolbar/ToolbarSelect.tsx +// In this example, we use it to create a replacement for the default Formatting +// Toolbar using MUI, but you can also use it to replace the default Link +// Toolbar. +function MUIToolbar(props: { children?: ReactNode }) { + return ( + + + + {props.children} + + + + ); +} + +// This replaces the default `FormattingToolbar` component with a simplified MUI +// version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/FormattingToolbar/FormattingToolbar.tsx +// You can remove any of the default selects/buttons, or add custom +// ones as children of the `MUIToolbar` component here. +export function CustomMUIFormattingToolbar() { + return ( + + + + {/* Replaces the `BasicTextStyleButton` component: */} + + + + + + + {/* Replaces the `TextAlignButton` component: */} + + + + + + + + + ); +} diff --git a/examples/03-ui-components/13-custom-ui/src/MUISideMenu.tsx b/examples/03-ui-components/13-custom-ui/src/MUISideMenu.tsx new file mode 100644 index 0000000000..62331421f8 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/src/MUISideMenu.tsx @@ -0,0 +1,188 @@ +import {} from "@blocknote/core"; +import { SideMenuExtension } from "@blocknote/core/extensions"; +import { + SideMenuProps, + useBlockNoteEditor, + useExtension, + useExtensionState, +} from "@blocknote/react"; +import { Delete, DragIndicator } from "@mui/icons-material"; +import { + Box, + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Typography, +} from "@mui/material"; +import { MouseEvent, ReactNode, useCallback, useState } from "react"; + +// This replaces the default `RemoveBlockItem` component with a simplified +// MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DefaultItems/RemoveBlockItem.tsx +function MUIRemoveBlockItem( + props: SideMenuProps & { closeDragHandleMenu: () => void }, +) { + const editor = useBlockNoteEditor(); + const sideMenu = useExtension(SideMenuExtension, { editor }); + + // Deletes the block next to the side menu. + const onClick = useCallback(() => { + sideMenu.unfreezeMenu(); + props.closeDragHandleMenu(); + editor.removeBlocks([editor.getTextCursorPosition().block]); + editor.focus(); + }, [props]); + + return ( + + + theme.palette.text.primary, + padding: "0.1em", + height: "0.8em", + width: "0.8em", + }} + /> + + + Delete Block + + + ); +} + +// This replaces the default `DragHandleMenu` component with a simplified MUI +// version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx +function MUIDragHandleMenu(props: { + anchorEl: HTMLElement | null; + container: Element; + onClose: () => void; + children: ReactNode; +}) { + return ( + + {props.children} + + ); +} + +// This replaces the default `DragHandleButton` component with a simplified MUI +// version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DefaultButtons/DragHandleButton.tsx +function MUIDragHandleButton(props: SideMenuProps) { + // Anchor/trigger element for the color menu. + const [anchorEl, setAnchorEl] = useState(null); + + const editor = useBlockNoteEditor(); + const sideMenu = useExtension(SideMenuExtension, { editor }); + const block = useExtensionState(SideMenuExtension, { + editor, + selector: (state) => state?.block, + }); + + // Handles opening and closing the drag handle menu. + const onClick = useCallback( + (event: MouseEvent) => { + sideMenu.freezeMenu(); + setAnchorEl(event.currentTarget); + }, + [sideMenu], + ); + const onClose = useCallback(() => { + setAnchorEl(null); + }, []); + + if (!block) { + return null; + } + + return ( + <> + sideMenu.blockDragStart(e, block)} + onDragEnd={sideMenu.blockDragEnd} + > + theme.palette.text.primary, + }} + /> + + + + + + ); +} + +// This replaces the generic Mantine `SideMenu` component: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/sideMenu/SideMenu.tsx +function MUISideMenu(props: SideMenuProps & { children: ReactNode }) { + // Since the side menu is positioned by the top-left corner of a block, we + // manually set its height based on the hovered block so that it's vertically + // centered. + const sideMenuHeight = useExtensionState(SideMenuExtension, { + selector: (state) => { + if (state && state.block.type === "heading") { + if (state.block.props.level === 1) { + return 78; + } + + if (state.block.props.level === 2) { + return 54; + } + + if (state.block.props.level === 3) { + return 37; + } + } + + return 30; + }, + }); + + return ( + + {props.children} + + ); +} + +// This replaces the default `SideMenu` component with a simplified MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/SideMenu.tsx +// You can add to or replace the `MUIDragHandleButton` component using the MUI +// `Button` components. Unlike the Formatting Toolbar, we don't use button +// components specific to the Side Menu since there is really nothing more to +// them than just an MUI `IconButton` and some styles passed via the `sx` prop, +// as you can see in `MUIDragHandleButton`. +export function CustomMUISideMenu(props: SideMenuProps) { + return ( + + + + ); +} diff --git a/examples/03-ui-components/13-custom-ui/src/MUISuggestionMenu.tsx b/examples/03-ui-components/13-custom-ui/src/MUISuggestionMenu.tsx new file mode 100644 index 0000000000..2574f5a68c --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/src/MUISuggestionMenu.tsx @@ -0,0 +1,177 @@ +import { + DefaultReactSuggestionItem, + elementOverflow, + SuggestionMenuProps, + useBlockNoteEditor, +} from "@blocknote/react"; +import { + Chip, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + ListSubheader, + Paper, +} from "@mui/material"; +import { useEffect, useMemo, useRef } from "react"; + +import { TextBlockSchema } from "./schema"; + +// If you want to change the items in a Suggestion Menu, like the Slash Menu, +// you don't need to modify any of the components in this file. Instead, you +// should change the array returned in the getItems` prop of the +// `SuggestionMenuController` in `App.tsx`. The components in this file are only +// responsible for rendering a Suggestion Menu, not setting its content. + +// This replaces the generic Mantine `SuggestionMenuItem` component with a +// simplified MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx +function MUISuggestionMenuItem( + props: Omit, "items"> & { + item: DefaultReactSuggestionItem & { index: number }; + }, +) { + const Icon = props.item.icon; + const editor = useBlockNoteEditor(); + + // Scrolls to the item if it's detected to overflow the Slash Menu. + const itemRef = useRef(null); + useEffect(() => { + if (!itemRef.current || props.item.index !== props.selectedIndex) { + return; + } + + const overflow = elementOverflow( + itemRef.current, + itemRef.current.closest( + `.MuiPaper-root:has([aria-label="suggestion-menu"])`, + )!, + ); + + if (overflow === "top") { + itemRef.current.scrollIntoView(true); + } else if (overflow === "bottom") { + itemRef.current.scrollIntoView(false); + } + }, [props.item.index, props.selectedIndex]); + + return ( + theme.palette.background.paper, + }} + > + { + props.onItemClick?.(props.item); + editor.focus(); + }} + > + {Icon} + + {props.item.badge && } + + + ); +} + +// This replaces the generic Mantine `EmptySuggestionMenuItem` component with a +// simplified MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/suggestionMenu/EmptySuggestionMenuItem.tsx +function MUIEmptySuggestionMenuItem() { + return ( + + + + + + ); +} + +// This replaces the generic Mantine `SuggestionMenuLabel` component with a +// simplified MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx +function MUISuggestionMenuLabel(props: { group: string }) { + return ( + theme.palette.background.paper, + }} + > + {props.group} + + ); +} + +// This replaces the generic Mantine `SuggestionMenu` component with a +// simplified MUI version: +// https://github.com/TypeCellOS/BlockNote/blob/main/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx +export function MUISuggestionMenu( + props: SuggestionMenuProps, +) { + // Sorts items into their groups. + const groups = useMemo(() => { + const groups: Record< + string, + (DefaultReactSuggestionItem & { index: number })[] + > = {}; + for (let i = 0; i < props.items.length; i++) { + const item = props.items[i]; + const group = item.group || item.title; + + if (!groups[group]) { + groups[group] = []; + } + + groups[group].push({ ...item, index: i }); + } + + return groups; + }, [props.items]); + + return ( + + + + ); +} diff --git a/examples/03-ui-components/13-custom-ui/src/schema.ts b/examples/03-ui-components/13-custom-ui/src/schema.ts new file mode 100644 index 0000000000..8a4d4f8db6 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/src/schema.ts @@ -0,0 +1,15 @@ +import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core"; + +// Simplified schema without media, file, and table blocks. +export const schema = BlockNoteSchema.create({ + blockSpecs: { + paragraph: defaultBlockSpecs.paragraph, + heading: defaultBlockSpecs.heading, + bulletListItem: defaultBlockSpecs.bulletListItem, + numberedListItem: defaultBlockSpecs.numberedListItem, + checkListItem: defaultBlockSpecs.checkListItem, + quote: defaultBlockSpecs.quote, + }, +}); + +export type TextBlockSchema = typeof schema.blockSchema; diff --git a/examples/03-ui-components/13-custom-ui/src/style.css b/examples/03-ui-components/13-custom-ui/src/style.css new file mode 100644 index 0000000000..6985372325 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/src/style.css @@ -0,0 +1,132 @@ +@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); + +.roboto-thin { + font-family: "Roboto", sans-serif; + font-weight: 100; + font-style: normal; +} + +.roboto-light { + font-family: "Roboto", sans-serif; + font-weight: 300; + font-style: normal; +} + +.roboto-regular { + font-family: "Roboto", sans-serif; + font-weight: 400; + font-style: normal; +} + +.roboto-medium { + font-family: "Roboto", sans-serif; + font-weight: 500; + font-style: normal; +} + +.roboto-bold { + font-family: "Roboto", sans-serif; + font-weight: 700; + font-style: normal; +} + +.roboto-black { + font-family: "Roboto", sans-serif; + font-weight: 900; + font-style: normal; +} + +.roboto-thin-italic { + font-family: "Roboto", sans-serif; + font-weight: 100; + font-style: italic; +} + +.roboto-light-italic { + font-family: "Roboto", sans-serif; + font-weight: 300; + font-style: italic; +} + +.roboto-regular-italic { + font-family: "Roboto", sans-serif; + font-weight: 400; + font-style: italic; +} + +.roboto-medium-italic { + font-family: "Roboto", sans-serif; + font-weight: 500; + font-style: italic; +} + +.roboto-bold-italic { + font-family: "Roboto", sans-serif; + font-weight: 700; + font-style: italic; +} + +.roboto-black-italic { + font-family: "Roboto", sans-serif; + font-weight: 900; + font-style: italic; +} + +.text-default { + color: var(--bn-colors-editor-text); +} + +.text-red { + color: var(--bn-colors-highlights-red-text); +} + +.text-orange { + color: var(--bn-colors-highlights-orange-text); +} + +.text-yellow { + color: var(--bn-colors-highlights-yellow-text); +} + +.text-green { + color: var(--bn-colors-highlights-green-text); +} + +.text-blue { + color: var(--bn-colors-highlights-blue-text); +} + +.text-purple { + color: var(--bn-colors-highlights-purple-text); +} + +.background-red { + background-color: var(--bn-colors-highlights-red-background); +} + +.background-orange { + background-color: var(--bn-colors-highlights-orange-background); +} + +.background-yellow { + background-color: var(--bn-colors-highlights-yellow-background); +} + +.background-green { + background-color: var(--bn-colors-highlights-green-background); +} + +.background-blue { + background-color: var(--bn-colors-highlights-blue-background); +} + +.background-purple { + background-color: var(--bn-colors-highlights-purple-background); +} + +/* Positions the formatting toolbar above the editor. */ +.bn-container { + display: flex; + flex-direction: column-reverse; + gap: 8px; +} diff --git a/examples/03-ui-components/13-custom-ui/tsconfig.json b/examples/03-ui-components/13-custom-ui/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/13-custom-ui/vite.config.ts b/examples/03-ui-components/13-custom-ui/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/13-custom-ui/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/.bnexample.json b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/.bnexample.json new file mode 100644 index 0000000000..16f9aea065 --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/.bnexample.json @@ -0,0 +1,11 @@ +{ + "playground": true, + "docs": true, + "author": "areknawo", + "tags": [ + "Intermediate", + "UI Components", + "Formatting Toolbar", + "Appearance & Styling" + ] +} diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/README.md b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/README.md new file mode 100644 index 0000000000..02eaf7673f --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/README.md @@ -0,0 +1,10 @@ +# Experimental Mobile Formatting Toolbar + +This example shows how to use the experimental mobile formatting toolbar, which uses [Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) to position the toolbar right above the virtual keyboard on mobile devices. + +Controller is currently marked **experimental** due to the flickering issue with positioning (caused by delays of the Visual Viewport API) + +**Relevant Docs:** + +- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/index.html b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/index.html new file mode 100644 index 0000000000..69b3583594 --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/index.html @@ -0,0 +1,14 @@ + + + + + Experimental Mobile Formatting Toolbar + + + +
+ + + diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/main.tsx b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json new file mode 100644 index 0000000000..688153f794 --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-experimental-mobile-formatting-toolbar", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/src/App.tsx b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/src/App.tsx new file mode 100644 index 0000000000..47d59e453c --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/src/App.tsx @@ -0,0 +1,39 @@ +import "@blocknote/core/fonts/inter.css"; +import { + ExperimentalMobileFormattingToolbarController, + useCreateBlockNote, +} from "@blocknote/react"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; + +import "./style.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: + "Check out the experimental mobile formatting toolbar by selecting some text (best experienced on a mobile device).", + }, + ], + }); + + // Renders the editor instance using a React component. + return ( + // Disables the default formatting toolbar and re-adds it without the + // `FormattingToolbarController` component. You may have seen + // `FormattingToolbarController` used in other examples, but we omit it here + // as we want to control the position and visibility ourselves. BlockNote + // also uses the `FormattingToolbarController` when displaying the + // Formatting Toolbar by default. + + + + ); +} diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/src/style.css b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/src/style.css new file mode 100644 index 0000000000..98e93611cd --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/src/style.css @@ -0,0 +1,9 @@ +.bn-container { + display: flex; + flex-direction: column-reverse; + gap: 8px; +} + +.bn-formatting-toolbar { + margin-inline: auto; +} diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/15-advanced-tables/.bnexample.json b/examples/03-ui-components/15-advanced-tables/.bnexample.json new file mode 100644 index 0000000000..9c4787320e --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["Intermediate", "UI Components", "Tables", "Appearance & Styling"] +} diff --git a/examples/03-ui-components/15-advanced-tables/README.md b/examples/03-ui-components/15-advanced-tables/README.md new file mode 100644 index 0000000000..448bf4bc19 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/README.md @@ -0,0 +1,13 @@ +# Advanced Tables + +This example enables the following features in tables: + +- Split cells +- Cell background color +- Cell text color +- Table row and column headers + +**Relevant Docs:** + +- [Tables](/docs/features/blocks/tables) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/15-advanced-tables/index.html b/examples/03-ui-components/15-advanced-tables/index.html new file mode 100644 index 0000000000..ac1e67a652 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/index.html @@ -0,0 +1,14 @@ + + + + + Advanced Tables + + + +
+ + + diff --git a/examples/03-ui-components/15-advanced-tables/main.tsx b/examples/03-ui-components/15-advanced-tables/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/15-advanced-tables/package.json b/examples/03-ui-components/15-advanced-tables/package.json new file mode 100644 index 0000000000..c11eac3efe --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-advanced-tables", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/15-advanced-tables/src/App.tsx b/examples/03-ui-components/15-advanced-tables/src/App.tsx new file mode 100644 index 0000000000..dfe89812a9 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/src/App.tsx @@ -0,0 +1,305 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // This enables the advanced table features + tables: { + splitCells: true, + cellBackgroundColor: true, + cellTextColor: true, + headers: true, + }, + initialContent: [ + { + id: "7e498b3d-d42e-4ade-9be0-054b292715ea", + type: "heading", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + level: 2, + }, + content: [ + { + type: "text", + text: "Advanced Tables", + styles: {}, + }, + ], + children: [], + }, + { + id: "cbf287c6-770b-413a-bff5-ad490a0b562a", + type: "table", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [199, 148, 201], + headerRows: 1, + rows: [ + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "This row has headers", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "This is ", + styles: {}, + }, + { + type: "text", + text: "RED", + styles: { + bold: true, + }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "red", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Text is Blue", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "blue", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "This spans 2 columns\nand 2 rows", + styles: {}, + }, + ], + props: { + colspan: 2, + rowspan: 2, + backgroundColor: "yellow", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Sooo many features", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "default", + textAlignment: "left", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "purple", + textAlignment: "left", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "A cell", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Another Cell", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "right", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Aligned center", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + ], + }, + ], + }, + children: [], + }, + { + id: "16e76a94-74e5-42e2-b461-fc9da9f381f7", + type: "paragraph", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Featuring:", + styles: {}, + }, + ], + children: [ + { + id: "785fc9f7-8554-47f4-a4df-8fe2f1438cac", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Cell background & foreground coloring", + styles: {}, + }, + ], + children: [], + }, + { + id: "1d0adf08-1b42-421a-b9ea-b3125dcc96d9", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Splitting & merging cells", + styles: {}, + }, + ], + children: [], + }, + { + id: "99991aa7-9d86-4d06-9073-b1a9c0329062", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Header row & column", + styles: {}, + }, + ], + children: [], + }, + ], + }, + { + id: "c7bf2a7c-8972-44f1-acd8-cf60fa734068", + type: "paragraph", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [], + children: [], + }, + ], + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/03-ui-components/15-advanced-tables/tsconfig.json b/examples/03-ui-components/15-advanced-tables/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/15-advanced-tables/vite.config.ts b/examples/03-ui-components/15-advanced-tables/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/15-advanced-tables/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/16-link-toolbar-buttons/.bnexample.json b/examples/03-ui-components/16-link-toolbar-buttons/.bnexample.json new file mode 100644 index 0000000000..b21b43636f --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": ["Intermediate", "Inline Content", "UI Components", "Link Toolbar"] +} diff --git a/examples/03-ui-components/16-link-toolbar-buttons/README.md b/examples/03-ui-components/16-link-toolbar-buttons/README.md new file mode 100644 index 0000000000..d561ca6f4f --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/README.md @@ -0,0 +1,9 @@ +# Adding Link Toolbar Buttons + +In this example, we add a button to the Link Toolbar which opens a browser alert. + +**Try it out:** Hover the link open the Link Toolbar, and click the new "Open Alert" button! + +**Relevant Docs:** + +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/16-link-toolbar-buttons/index.html b/examples/03-ui-components/16-link-toolbar-buttons/index.html new file mode 100644 index 0000000000..8d742eb82a --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/index.html @@ -0,0 +1,14 @@ + + + + + Adding Link Toolbar Buttons + + + +
+ + + diff --git a/examples/03-ui-components/16-link-toolbar-buttons/main.tsx b/examples/03-ui-components/16-link-toolbar-buttons/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/16-link-toolbar-buttons/package.json b/examples/03-ui-components/16-link-toolbar-buttons/package.json new file mode 100644 index 0000000000..528d13905b --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-link-toolbar-buttons", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/02-ui-components/link-toolbar-buttons/AlertButton.tsx b/examples/03-ui-components/16-link-toolbar-buttons/src/AlertButton.tsx similarity index 82% rename from examples/02-ui-components/link-toolbar-buttons/AlertButton.tsx rename to examples/03-ui-components/16-link-toolbar-buttons/src/AlertButton.tsx index b393d6077e..36cf50d938 100644 --- a/examples/02-ui-components/link-toolbar-buttons/AlertButton.tsx +++ b/examples/03-ui-components/16-link-toolbar-buttons/src/AlertButton.tsx @@ -7,9 +7,8 @@ export function AlertButton(props: LinkToolbarProps) { return ( { - window.alert(`Link URL: ${props.url}`); - }}> + onClick={() => window.alert(`Link URL: ${props.url}`)} + > Open Alert ); diff --git a/examples/03-ui-components/16-link-toolbar-buttons/src/App.tsx b/examples/03-ui-components/16-link-toolbar-buttons/src/App.tsx new file mode 100644 index 0000000000..52504b56bf --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/src/App.tsx @@ -0,0 +1,72 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + DeleteLinkButton, + EditLinkButton, + LinkToolbar, + LinkToolbarController, + LinkToolbarProps, + OpenLinkButton, + useCreateBlockNote, +} from "@blocknote/react"; + +import { AlertButton } from "./AlertButton"; + +const CustomLinkToolbar = (props: LinkToolbarProps) => ( + + + + + {/* Extra button to open alert. */} + + +); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Hover the link below to see the modified Link Toolbar", + }, + { + type: "paragraph", + content: [ + { + type: "link", + href: "https://www.blocknotejs.org/", + content: [ + { + type: "text", + text: "Home Page", + styles: {}, + }, + ], + }, + ], + }, + ], + }); + + // Renders the editor instance. + return ( + + + + ); +} diff --git a/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json b/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts b/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/16-link-toolbar-buttons/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/17-advanced-tables-2/.bnexample.json b/examples/03-ui-components/17-advanced-tables-2/.bnexample.json new file mode 100644 index 0000000000..79caa77b3e --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "must", + "tags": ["Intermediate", "UI Components", "Tables", "Appearance & Styling"] +} diff --git a/examples/03-ui-components/17-advanced-tables-2/README.md b/examples/03-ui-components/17-advanced-tables-2/README.md new file mode 100644 index 0000000000..827e0c1428 --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/README.md @@ -0,0 +1,54 @@ +# Advanced Tables with Calculated Columns + +This example demonstrates advanced table features including automatic calculations. It shows how to create a table with calculated columns that automatically update when values change. + +## Features + +- **Automatic Calculations**: Quantity × Price = Total for each row +- **Grand Total**: Automatically calculated sum of all totals +- **Real-time Updates**: Calculations update immediately when you change quantity or price values +- **Split cells**: Merge and split table cells +- **Cell background color**: Color individual cells +- **Cell text color**: Change text color in cells +- **Table row and column headers**: Use headers for better organization + +## How It Works + +The example uses the `onChange` event listener to detect when table content changes. When a table is updated, it automatically: + +1. Extracts quantity and price values from each data row +2. Calculates the total (quantity × price) for each row +3. Updates the total column with the calculated values +4. Calculates and updates the grand total + +## Code Highlights + +```tsx + { + const changes = getChanges(); + + changes.forEach((change) => { + if (change.type === "update" && change.block.type === "table") { + const updatedRows = calculateTableTotals(change.block); + if (updatedRows) { + editor.updateBlock(change.block, { + type: "table", + content: { + ...change.block.content, + rows: updatedRows as any, + } as any, + }); + } + } + }); + }} +> +``` + +**Relevant Docs:** + +- [Tables](/docs/features/blocks/tables) +- [Editor Setup](/docs/getting-started/editor-setup) +- [Events](/docs/reference/editor/events) diff --git a/examples/03-ui-components/17-advanced-tables-2/index.html b/examples/03-ui-components/17-advanced-tables-2/index.html new file mode 100644 index 0000000000..8c97f71cc4 --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/index.html @@ -0,0 +1,14 @@ + + + + + Advanced Tables with Calculated Columns + + + +
+ + + diff --git a/examples/03-ui-components/17-advanced-tables-2/main.tsx b/examples/03-ui-components/17-advanced-tables-2/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/17-advanced-tables-2/package.json b/examples/03-ui-components/17-advanced-tables-2/package.json new file mode 100644 index 0000000000..0057846e4b --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-advanced-tables-2", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/17-advanced-tables-2/src/App.tsx b/examples/03-ui-components/17-advanced-tables-2/src/App.tsx new file mode 100644 index 0000000000..ad6390d0ff --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/src/App.tsx @@ -0,0 +1,689 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import type { Block, DefaultBlockSchema } from "@blocknote/core"; +import { useRef } from "react"; + +export default function App() { + const applying = useRef(false); + + // Creates a new editor instance. + const editor = useCreateBlockNote({ + // This enables the advanced table features + tables: { + splitCells: true, + cellBackgroundColor: true, + cellTextColor: true, + headers: true, + }, + initialContent: [ + { + id: "7e498b3d-d42e-4ade-9be0-054b292715ea", + type: "heading", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + level: 2, + }, + content: [ + { + type: "text", + text: "Advanced Tables with Calculated Columns", + styles: {}, + }, + ], + children: [], + }, + { + id: "cbf287c6-770b-413a-bff5-ad490a0b562a", + type: "table", + props: { + textColor: "default", + }, + content: { + type: "tableContent", + columnWidths: [150, 120, 120, 120], + headerRows: 1, + rows: [ + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Item", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Quantity", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Price ($)", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "Total ($)", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "blue", + textColor: "white", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Laptop", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "2", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "1200", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "2400", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "green", + textColor: "white", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Mouse", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "5", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "25", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "125", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "green", + textColor: "white", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Keyboard", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "left", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "3", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "80", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "240", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "green", + textColor: "white", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Grand Total", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "yellow", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "yellow", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "", + styles: {}, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "yellow", + textColor: "default", + textAlignment: "center", + }, + }, + { + type: "tableCell", + content: [ + { + type: "text", + text: "2765", + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "red", + textColor: "white", + textAlignment: "center", + }, + }, + ], + }, + ], + }, + children: [], + }, + { + id: "16e76a94-74e5-42e2-b461-fc9da9f381f7", + type: "paragraph", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Features:", + styles: {}, + }, + ], + children: [ + { + id: "785fc9f7-8554-47f4-a4df-8fe2f1438cac", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Automatic calculation of totals (Quantity × Price)", + styles: {}, + }, + ], + children: [], + }, + { + id: "1d0adf08-1b42-421a-b9ea-b3125dcc96d9", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Grand total calculation", + styles: {}, + }, + ], + children: [], + }, + { + id: "99991aa7-9d86-4d06-9073-b1a9c0329062", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Cell background & foreground coloring", + styles: {}, + }, + ], + children: [], + }, + { + id: "c7bf2a7c-8972-44f1-acd8-cf60fa734068", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Splitting & merging cells", + styles: {}, + }, + ], + children: [], + }, + { + id: "785fc9f7-8554-47f4-a4df-8fe2f1438cac", + type: "bulletListItem", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [ + { + type: "text", + text: "Header rows & columns", + styles: {}, + }, + ], + children: [], + }, + ], + }, + { + id: "c7bf2a7c-8972-44f1-acd8-cf60fa734068", + type: "paragraph", + props: { + textColor: "default", + backgroundColor: "default", + textAlignment: "left", + }, + content: [], + children: [], + }, + ], + }); + + // Function to calculate totals for a table + const calculateTableTotals = (tableBlock: Block) => { + if (tableBlock.type !== "table") { + return; + } + + const rows = tableBlock.content.rows; + if (rows.length < 2) { + return; + } // Need at least header + 1 data row + + let grandTotal = 0; + const updatedRows = rows.map((row, rowIndex: number) => { + if (rowIndex === 0) { + return row; + } // Skip header row + if (rowIndex === rows.length - 1) { + return row; + } // Skip grand total row + + // Helper function to extract text from a cell + const getCellText = (cell: any): string => { + if (typeof cell === "string") { + return cell; + } + if (cell && typeof cell === "object" && "content" in cell) { + return cell.content?.[0]?.text || "0"; + } + return "0"; + }; + + const itemText = getCellText(row.cells[0]); + const quantityText = getCellText(row.cells[1]); + const priceText = getCellText(row.cells[2]); + + const quantity = parseFloat(quantityText) || 0; + const price = parseFloat(priceText) || 0; + const total = quantity * price; + + grandTotal += total; + + // Update the total cell + const updatedCells = [...row.cells]; + updatedCells[3] = { + type: "tableCell", + content: [ + { + type: "text", + text: total.toString(), + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "green", + textColor: "white", + textAlignment: "center", + }, + }; + + // Update item label if total is above 4k + const baseItemText = itemText.replace(" (eligible for discount)", ""); + if (total >= 4000) { + updatedCells[0] = { + ...row.cells[0], + content: [ + { + type: "text", + text: baseItemText + " (eligible for discount)", + styles: {}, + }, + ], + }; + } else { + updatedCells[0] = { + ...row.cells[0], + content: [ + { + type: "text", + text: baseItemText, + styles: {}, + }, + ], + }; + } + + return { + ...row, + cells: updatedCells, + }; + }); + + // Update grand total row + const grandTotalRow = updatedRows[rows.length - 1]; + if (grandTotalRow) { + const updatedGrandTotalCells = [...grandTotalRow.cells]; + updatedGrandTotalCells[3] = { + type: "tableCell", + content: [ + { + type: "text", + text: grandTotal.toString(), + styles: { bold: true }, + }, + ], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "red", + textColor: "white", + textAlignment: "center", + }, + }; + + updatedRows[rows.length - 1] = { + ...grandTotalRow, + cells: updatedGrandTotalCells, + }; + } + + return updatedRows as typeof tableBlock.content.rows; + }; + + // Renders the editor instance using a React component. + return ( + { + const changes = getChanges(); + + if (changes.length === 0 || applying.current) { + return; + } + + // prevents a double onChange because we're updating the block here + applying.current = true; + + changes.forEach((change) => { + if (change.type === "update" && change.block.type === "table") { + const updatedRows = calculateTableTotals(change.block); + if (updatedRows) { + // Use any type to bypass complex type checking for this demo + editor.updateBlock(change.block, { + type: "table", + content: { + ...change.block.content, + rows: updatedRows, + }, + }); + } + } + }); + + requestAnimationFrame(() => (applying.current = false)); + }} + > + ); +} diff --git a/examples/03-ui-components/17-advanced-tables-2/tsconfig.json b/examples/03-ui-components/17-advanced-tables-2/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/17-advanced-tables-2/vite.config.ts b/examples/03-ui-components/17-advanced-tables-2/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/17-advanced-tables-2/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/18-drag-n-drop/.bnexample.json b/examples/03-ui-components/18-drag-n-drop/.bnexample.json new file mode 100644 index 0000000000..c2c8b67c60 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": false, + "author": "nperez0111", + "tags": ["Intermediate", "UI Components", "Drag & Drop", "Customization"] +} diff --git a/examples/03-ui-components/18-drag-n-drop/README.md b/examples/03-ui-components/18-drag-n-drop/README.md new file mode 100644 index 0000000000..6746cf86a3 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/README.md @@ -0,0 +1,47 @@ +# Drag & Drop Exclusion + +This example demonstrates how to use the `DRAG_EXCLUSION_CLASSNAME` to create separate drag & drop areas that don't interfere with BlockNote's built-in block drag & drop functionality. + +## Features + +- **Drag Exclusion**: Elements with the `bn-drag-exclude` classname are treated as separate drag & drop operations +- **Independent Drag Areas**: Create custom drag & drop functionality alongside BlockNote's editor +- **No Interference**: Custom drag operations won't trigger BlockNote's block reordering +- **Side-by-side Demo**: Shows the editor and custom drag area working independently + +## How It Works + +By adding the `DRAG_EXCLUSION_CLASSNAME` (`bn-drag-exclude`) to an element, you tell BlockNote's drag & drop handlers to ignore all drag events within that element and its children. This allows you to implement your own custom drag & drop logic without conflicts. + +The exclusion check works by traversing up the DOM tree from the drag event target, checking if any ancestor has the exclusion classname. If found, BlockNote's handlers return early, leaving your custom handlers in full control. + +## Code Highlights + +### Import the constant: + +```tsx +import { DRAG_EXCLUSION_CLASSNAME } from "@blocknote/core"; +``` + +### Apply it to your custom drag area: + +```tsx +
+ {/* Your custom drag & drop UI */} +
+ Custom draggable items +
+
+``` + +## Use Cases + +- **Custom UI elements**: Add draggable components within or near the editor +- **File upload areas**: Create drag-and-drop file upload zones +- **Sortable lists**: Implement custom sortable lists alongside the editor +- **External integrations**: Integrate with third-party drag & drop libraries + +**Relevant Docs:** + +- [Side Menu (Drag Handle)](/docs/react/components/side-menu) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/18-drag-n-drop/index.html b/examples/03-ui-components/18-drag-n-drop/index.html new file mode 100644 index 0000000000..a10c552621 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/index.html @@ -0,0 +1,14 @@ + + + + + Drag & Drop Exclusion + + + +
+ + + diff --git a/examples/03-ui-components/18-drag-n-drop/main.tsx b/examples/03-ui-components/18-drag-n-drop/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/18-drag-n-drop/package.json b/examples/03-ui-components/18-drag-n-drop/package.json new file mode 100644 index 0000000000..1acd194ed5 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-drag-n-drop", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/18-drag-n-drop/src/App.tsx b/examples/03-ui-components/18-drag-n-drop/src/App.tsx new file mode 100644 index 0000000000..e90596f427 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/src/App.tsx @@ -0,0 +1,133 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { useState } from "react"; +import "./styles.css"; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to the Drag & Drop Exclusion demo!", + }, + { + type: "paragraph", + content: + "Try dragging the blocks in the editor - they will work as normal.", + }, + { + type: "paragraph", + content: + "Now try dragging the colored boxes on the right - they won't interfere with the editor's drag & drop!", + }, + ], + }); + + const [draggedItems, setDraggedItems] = useState([ + { id: "1", color: "#FF6B6B", label: "Red Item" }, + { id: "2", color: "#4ECDC4", label: "Teal Item" }, + { id: "3", color: "#45B7D1", label: "Blue Item" }, + { id: "4", color: "#FFA07A", label: "Orange Item" }, + ]); + + const [droppedItems, setDroppedItems] = useState([]); + + const handleDragStart = ( + e: React.DragEvent, + item: (typeof draggedItems)[0], + ) => { + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("custom-item", JSON.stringify(item)); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + const data = e.dataTransfer.getData("custom-item"); + if (data) { + const item = JSON.parse(data); + setDroppedItems((prev) => [...prev, item]); + setDraggedItems((prev) => prev.filter((i) => i.id !== item.id)); + } + }; + + const handleReset = () => { + setDraggedItems([ + { id: "1", color: "#FF6B6B", label: "Red Item" }, + { id: "2", color: "#4ECDC4", label: "Teal Item" }, + { id: "3", color: "#45B7D1", label: "Blue Item" }, + { id: "4", color: "#FFA07A", label: "Orange Item" }, + ]); + setDroppedItems([]); + }; + + return ( +
+
+

BlockNote Editor

+ +
+ +
+

Separate Drag & Drop Area

+

+ This area uses the bn-drag-exclude classname, so dragging + items here won't interfere with the editor. +

+ +
+
+

Draggable Items

+
+ {draggedItems.map((item) => ( +
handleDragStart(e, item)} + style={{ backgroundColor: item.color }} + > + {item.label} +
+ ))} +
+
+ +
+

Drop Zone

+
+ {droppedItems.length === 0 ? ( +

Drop items here

+ ) : ( + droppedItems.map((item) => ( +
+ {item.label} +
+ )) + )} +
+
+
+ + +
+
+ ); +} diff --git a/examples/03-ui-components/18-drag-n-drop/src/styles.css b/examples/03-ui-components/18-drag-n-drop/src/styles.css new file mode 100644 index 0000000000..4c8d78b9f4 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/src/styles.css @@ -0,0 +1,154 @@ +.app-container { + display: flex; + gap: 2rem; + padding: 2rem; + max-width: 1400px; + margin: 0 auto; + height: 100vh; +} + +.editor-section { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.editor-section h2 { + margin-top: 0; + margin-bottom: 1rem; + font-size: 1.5rem; + color: #333; +} + +.drag-demo-section { + flex: 0 0 400px; + display: flex; + flex-direction: column; + background: #f8f9fa; + border-radius: 8px; + padding: 1.5rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.drag-demo-section h2 { + margin-top: 0; + margin-bottom: 0.5rem; + font-size: 1.5rem; + color: #333; +} + +.info-text { + font-size: 0.875rem; + color: #666; + margin-bottom: 1.5rem; + line-height: 1.5; +} + +.info-text code { + background: #e9ecef; + padding: 0.125rem 0.375rem; + border-radius: 3px; + font-family: monospace; + font-size: 0.85em; + color: #c7254e; +} + +.drag-columns { + display: flex; + gap: 1rem; + flex: 1; + min-height: 0; +} + +.drag-column { + flex: 1; + display: flex; + flex-direction: column; +} + +.drag-column h3 { + margin-top: 0; + margin-bottom: 0.75rem; + font-size: 1rem; + color: #495057; + font-weight: 600; +} + +.items-container { + flex: 1; + background: white; + border: 2px dashed #dee2e6; + border-radius: 6px; + padding: 0.75rem; + min-height: 200px; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.drop-zone .items-container { + border-color: #4ecdc4; + background: #f0fffe; +} + +.draggable-item { + padding: 0.75rem 1rem; + border-radius: 6px; + color: white; + font-weight: 500; + cursor: move; + user-select: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.draggable-item:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +.draggable-item:active { + cursor: grabbing; + transform: scale(0.95); +} + +.placeholder { + color: #adb5bd; + text-align: center; + margin: auto; + font-style: italic; +} + +.reset-button { + margin-top: 1rem; + padding: 0.625rem 1.25rem; + background: #495057; + color: white; + border: none; + border-radius: 6px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; +} + +.reset-button:hover { + background: #343a40; +} + +.reset-button:active { + transform: scale(0.98); +} + +@media (max-width: 1024px) { + .app-container { + flex-direction: column; + height: auto; + } + + .drag-demo-section { + flex: 0 0 auto; + } +} diff --git a/examples/03-ui-components/18-drag-n-drop/tsconfig.json b/examples/03-ui-components/18-drag-n-drop/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/18-drag-n-drop/vite.config.ts b/examples/03-ui-components/18-drag-n-drop/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/.bnexample.json b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/.bnexample.json new file mode 100644 index 0000000000..8aa4573b06 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/.bnexample.json @@ -0,0 +1,12 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Suggestion Menus", + "Slash Menu" + ] +} diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/README.md b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/README.md new file mode 100644 index 0000000000..76f3061571 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/README.md @@ -0,0 +1,11 @@ +# Slash Menu Grouping & Ordering + +In this example, we filter and reorder the default Slash Menu items so that only the "Basic blocks" and "Headings" groups are shown, with "Basic blocks" appearing first. + +**Try it out:** Press the "/" key to open the Slash Menu and see the reordered groups! + +**Relevant Docs:** + +- [Item Grouping & Ordering](/docs/react/components/suggestion-menus) +- [Changing Slash Menu Items](/docs/react/components/suggestion-menus) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/index.html b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/index.html new file mode 100644 index 0000000000..405a9fc360 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/index.html @@ -0,0 +1,14 @@ + + + + + Slash Menu Grouping & Ordering + + + +
+ + + diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/main.tsx b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/package.json b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/package.json new file mode 100644 index 0000000000..e870d856d4 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-suggestion-menus-grouping-ordering", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/src/App.tsx b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/src/App.tsx new file mode 100644 index 0000000000..93480ff122 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/src/App.tsx @@ -0,0 +1,60 @@ +import { BlockNoteEditor } from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + DefaultReactSuggestionItem, + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; + +// Returns the default Slash Menu items, keeping only the "Basic blocks" and +// "Headings" groups, with "Basic blocks" listed before "Headings". +const getCustomSlashMenuItems = ( + editor: BlockNoteEditor, +): DefaultReactSuggestionItem[] => { + const defaultItems = getDefaultReactSlashMenuItems(editor); + + const basicBlocks = defaultItems.filter( + (item) => item.group === "Basic blocks", + ); + const headings = defaultItems.filter((item) => item.group === "Headings"); + + return [...basicBlocks, ...headings]; +}; + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: "Press the '/' key to open the Slash Menu", + }, + { + type: "paragraph", + content: + "Notice that only 'Basic blocks' and 'Headings' are shown, in that order", + }, + ], + }); + + // Renders the editor instance. + return ( + + + filterSuggestionItems(getCustomSlashMenuItems(editor), query) + } + /> + + ); +} diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/03-ui-components/20-portal-elements/.bnexample.json b/examples/03-ui-components/20-portal-elements/.bnexample.json new file mode 100644 index 0000000000..40dfffd4d9 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": ["UI Components", "Advanced"] +} diff --git a/examples/03-ui-components/20-portal-elements/README.md b/examples/03-ui-components/20-portal-elements/README.md new file mode 100644 index 0000000000..38a5926e3c --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/README.md @@ -0,0 +1,13 @@ +# Configuring Portal Targets + +By default, BlockNote's floating UI elements (formatting toolbar, slash menu, table handles, etc.) mount inside the editor's `bn-container`. The `portalElements` prop on `BlockNoteView` lets you change that — globally via `default`, or per element by key. + +This example renders two editors side-by-side, both wrapped in a small `overflow: hidden` container. The left editor uses the default — the slash menu is clipped by the editor's bounds. The right editor passes `portalElements={{ default: document.body }}` so floating UI escapes the wrapper and renders fully. + +```tsx + +``` + +**Relevant Docs:** + +- [UI Components](/docs/react/components) diff --git a/examples/03-ui-components/20-portal-elements/index.html b/examples/03-ui-components/20-portal-elements/index.html new file mode 100644 index 0000000000..e43d537d45 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/index.html @@ -0,0 +1,14 @@ + + + + + Configuring Portal Targets + + + +
+ + + diff --git a/examples/03-ui-components/20-portal-elements/main.tsx b/examples/03-ui-components/20-portal-elements/main.tsx new file mode 100644 index 0000000000..1260513388 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./src/App.jsx"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + , +); diff --git a/examples/03-ui-components/20-portal-elements/package.json b/examples/03-ui-components/20-portal-elements/package.json new file mode 100644 index 0000000000..d4563dc4fe --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-ui-components-portal-elements", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vp dev", + "dev": "vp dev", + "build:prod": "tsc && vp build", + "preview": "vp preview" + }, + "dependencies": { + "@blocknote/ariakit": "latest", + "@blocknote/core": "latest", + "@blocknote/mantine": "latest", + "@blocknote/react": "latest", + "@blocknote/shadcn": "latest", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite-plus": "catalog:" + } +} diff --git a/examples/03-ui-components/20-portal-elements/src/App.tsx b/examples/03-ui-components/20-portal-elements/src/App.tsx new file mode 100644 index 0000000000..f450fac7e1 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/src/App.tsx @@ -0,0 +1,57 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote, type PortalElementsMap } from "@blocknote/react"; + +import "./styles.css"; + +const initialContent = [ + { + type: "paragraph" as const, + content: "Click in this editor and press / to open the slash menu.", + }, + { + type: "paragraph" as const, + content: "Notice whether the menu fits inside the box or escapes it.", + }, + { + type: "paragraph" as const, + }, +]; + +function PortalDemoEditor({ + label, + description, + portalElements, +}: { + label: string; + description: string; + portalElements?: PortalElementsMap; +}) { + const editor = useCreateBlockNote({ initialContent }); + return ( +
+
{label}
+
{description}
+
+ +
+
+ ); +} + +export default function App() { + return ( +
+ + +
+ ); +} diff --git a/examples/03-ui-components/20-portal-elements/src/styles.css b/examples/03-ui-components/20-portal-elements/src/styles.css new file mode 100644 index 0000000000..8cf28385d0 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/src/styles.css @@ -0,0 +1,68 @@ +.views { + container-name: views; + container-type: inline-size; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 8px; + padding: 8px; +} + +/* + * Each view is intentionally shorter than the slash menu so the clipping + * vs escaping behaviour is visible at a glance. + */ +.view-wrapper { + display: flex; + flex-direction: column; + height: 260px; + width: 100%; +} + +@container views (width > 1024px) { + .view-wrapper { + width: calc(50% - 4px); + } +} + +.view-label { + color: #0090ff; + display: flex; + font-size: 12px; + font-weight: bold; + justify-content: space-between; + margin-inline: 16px; +} + +.view-description { + color: #0090ff; + font-size: 12px; + margin: 2px 16px 0; +} + +/* + * `position: relative` is what actually makes `overflow: hidden` clip the + * absolutely-positioned floating UI. Without it the popover's containing + * block is the viewport and the clip is bypassed. + */ +.view { + border: solid #0090ff 1px; + border-radius: 16px; + flex: 1; + height: 0; + padding: 8px; + position: relative; + overflow: hidden; +} + +.view .bn-container { + height: 100%; + margin: 0; + max-width: none; + padding: 0; +} + +.view .bn-editor { + height: 100%; + overflow: auto; +} diff --git a/examples/03-ui-components/20-portal-elements/tsconfig.json b/examples/03-ui-components/20-portal-elements/tsconfig.json new file mode 100644 index 0000000000..93fa81bee8 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/tsconfig.json @@ -0,0 +1,29 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": ["."], + "__ADD_FOR_LOCAL_DEV_references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} diff --git a/examples/03-ui-components/20-portal-elements/vite.config.ts b/examples/03-ui-components/20-portal-elements/vite.config.ts new file mode 100644 index 0000000000..0133a6da9e --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/vite.config.ts @@ -0,0 +1,31 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite-plus"; +// https://vitejs.dev/config/ +export default defineConfig(((conf: { command: string }) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/", + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/", + ), + } as any), + }, +})) as Parameters[0]); diff --git a/examples/04-interoperability/01-converting-blocks-to-html/App.tsx b/examples/04-interoperability/01-converting-blocks-to-html/App.tsx deleted file mode 100644 index e10fe0c57f..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/App.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useState } from "react"; - -import "./styles.css"; - -export default function App() { - // Stores the editor's contents as HTML. - const [html, setHTML] = useState(""); - - // Creates a new editor instance with some initial content. - const editor = useCreateBlockNote({ - initialContent: [ - { - type: "paragraph", - content: [ - "Hello, ", - { - type: "text", - text: "world!", - styles: { - bold: true, - }, - }, - ], - }, - ], - }); - - const onChange = async () => { - // Converts the editor's contents from Block objects to HTML and store to state. - const html = await editor.blocksToHTMLLossy(editor.document); - setHTML(html); - }; - - // Renders the editor instance, and its contents as HTML below. - return ( -
-
Input (BlockNote Editor):
-
- -
-
Output (HTML):
-
-
-          {html}
-        
-
-
- ); -} diff --git a/examples/04-interoperability/01-converting-blocks-to-html/README.md b/examples/04-interoperability/01-converting-blocks-to-html/README.md deleted file mode 100644 index 8288bba270..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Converting Blocks to HTML - -This example exports the current document (all blocks) as HTML and displays it below the editor. - -**Try it out:** Edit the document to see the HTML representation! - -**Relevant Docs:** - -- [Converting Blocks to HTML](/docs/editor-api/converting-blocks#converting-blocks-to-html) diff --git a/examples/04-interoperability/01-converting-blocks-to-html/index.html b/examples/04-interoperability/01-converting-blocks-to-html/index.html deleted file mode 100644 index de84a6cd5a..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Converting Blocks to HTML - - -
- - - diff --git a/examples/04-interoperability/01-converting-blocks-to-html/main.tsx b/examples/04-interoperability/01-converting-blocks-to-html/main.tsx deleted file mode 100644 index f88b490fbd..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import React from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/04-interoperability/01-converting-blocks-to-html/package.json b/examples/04-interoperability/01-converting-blocks-to-html/package.json deleted file mode 100644 index 9a5ee01ef9..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-converting-blocks-to-html", - "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "private": true, - "version": "0.12.4", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "eslint . --max-warnings 0" - }, - "dependencies": { - "@blocknote/core": "latest", - "@blocknote/react": "latest", - "@blocknote/ariakit": "latest", - "@blocknote/mantine": "latest", - "@blocknote/shadcn": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.10.0", - "vite": "^4.4.8" - }, - "eslintConfig": { - "extends": [ - "../../../.eslintrc.js" - ] - }, - "eslintIgnore": [ - "dist" - ] -} \ No newline at end of file diff --git a/examples/04-interoperability/01-converting-blocks-to-html/styles.css b/examples/04-interoperability/01-converting-blocks-to-html/styles.css deleted file mode 100644 index 89d965109f..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/styles.css +++ /dev/null @@ -1,25 +0,0 @@ -.wrapper { - display: flex; - flex-direction: column; - height: 100%; -} - -.item { - border-radius: 0.5rem; - flex: 1; - overflow: hidden; -} - -.item.bordered { - border: 1px solid gray; -} - -.item pre { - border-radius: 0.5rem; - height: 100%; - overflow: auto; - padding-block: 1rem; - padding-inline: 54px; - width: 100%; - white-space: pre-wrap; -} \ No newline at end of file diff --git a/examples/04-interoperability/01-converting-blocks-to-html/tsconfig.json b/examples/04-interoperability/01-converting-blocks-to-html/tsconfig.json deleted file mode 100644 index 1bd8ab3c57..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "composite": true - }, - "include": [ - "." - ], - "__ADD_FOR_LOCAL_DEV_references": [ - { - "path": "../../../packages/core/" - }, - { - "path": "../../../packages/react/" - } - ] -} \ No newline at end of file diff --git a/examples/04-interoperability/01-converting-blocks-to-html/vite.config.ts b/examples/04-interoperability/01-converting-blocks-to-html/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/04-interoperability/01-converting-blocks-to-html/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -import react from "@vitejs/plugin-react"; -import * as fs from "fs"; -import * as path from "path"; -import { defineConfig } from "vite"; -// import eslintPlugin from "vite-plugin-eslint"; -// https://vitejs.dev/config/ -export default defineConfig((conf) => ({ - plugins: [react()], - optimizeDeps: {}, - build: { - sourcemap: true, - }, - resolve: { - alias: - conf.command === "build" || - !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) - ? {} - : ({ - // Comment out the lines below to load a built version of blocknote - // or, keep as is to load live from sources with live reload working - "@blocknote/core": path.resolve( - __dirname, - "../../packages/core/src/" - ), - "@blocknote/react": path.resolve( - __dirname, - "../../packages/react/src/" - ), - } as any), - }, -})); diff --git a/examples/04-interoperability/02-converting-blocks-from-html/App.tsx b/examples/04-interoperability/02-converting-blocks-from-html/App.tsx deleted file mode 100644 index 8b53b5ac9c..0000000000 --- a/examples/04-interoperability/02-converting-blocks-from-html/App.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { ChangeEvent, useCallback, useEffect } from "react"; - -import "./styles.css"; - -const initialHTML = "

Hello, world!

"; - -export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote(); - - const htmlInputChanged = useCallback( - async (e: ChangeEvent) => { - // Whenever the current HTML content changes, converts it to an array of - // Block objects and replaces the editor's content with them. - const blocks = await editor.tryParseHTMLToBlocks(e.target.value); - editor.replaceBlocks(editor.document, blocks); - }, - [editor] - ); - - // For initialization; on mount, convert the initial HTML to blocks and replace the default editor's content - useEffect(() => { - async function loadInitialHTML() { - const blocks = await editor.tryParseHTMLToBlocks(initialHTML); - editor.replaceBlocks(editor.document, blocks); - } - loadInitialHTML(); - }, [editor]); - - // Renders a text area for you to write/paste HTML in, and the editor instance - // below, which displays the current HTML as blocks. - return ( -
-
Input (HTML):
-
- -